From fe3be65bf7de93db610c9fdee9eafeceff790836 Mon Sep 17 00:00:00 2001
From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com>
Date: Fri, 24 Jul 2026 13:01:14 +0800
Subject: [PATCH 01/14] wip
---
.../org/dreeam/leaf/config/ConfigModule.java | 22 +++
.../leaf/config/ConfigPathMigration.java | 38 +++++
.../org/dreeam/leaf/config/LeafConfig.java | 132 +++++++++++++--
.../dreeam/leaf/config/LeafGlobalConfig.java | 21 ++-
.../dreeam/leaf/config/LeafWorldConfig.java | 152 ++++++++++++++++++
.../dreeam/leaf/config/WorldConfigModule.java | 7 +
.../leaf/config/modules/misc/SecureSeed.java | 37 +++--
.../OptimizeNonFlushPacketSending.java | 4 +
.../src/main/java/su/plo/matter/Globals.java | 2 +-
9 files changed, 388 insertions(+), 27 deletions(-)
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/ConfigPathMigration.java
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/LeafWorldConfig.java
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/WorldConfigModule.java
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModule.java b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModule.java
index c3ac3532eb..5f8647a8c0 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModule.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModule.java
@@ -12,6 +12,7 @@
public abstract class ConfigModule extends LeafConfig {
private static final Set LOADED_MODULES = new HashSet<>();
+ private static List> WORLD_MODULES = List.of();
protected final LeafGlobalConfig globalConfig;
@@ -22,6 +23,7 @@ public ConfigModule() {
public static void initModules() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
List enabledExperimentalModules = new ArrayList<>();
List deprecatedModules = new ArrayList<>();
+ List> worldModules = new ArrayList<>();
Class>[] classes = LeafConfig.getClasses(LeafConfig.CONFIG_MODULE_PACKAGE).toArray(new Class[0]);
ObjectArrays.quickSort(classes, Comparator.comparing(Class::getSimpleName));
@@ -29,6 +31,12 @@ public static void initModules() throws NoSuchMethodException, InvocationTargetE
ConfigModule module = (ConfigModule) clazz.getConstructor().newInstance();
module.onLoaded();
+ if (WorldConfigModule.class.isAssignableFrom(clazz)) {
+ @SuppressWarnings("unchecked")
+ Class extends WorldConfigModule> worldModuleClass = (Class extends WorldConfigModule>) clazz;
+ worldModules.add(worldModuleClass);
+ }
+
LOADED_MODULES.add(module);
for (Field field : getAnnotatedStaticFields(clazz, Experimental.class)) {
if (!(field.get(null) instanceof Boolean enabled)) continue;
@@ -51,6 +59,8 @@ public static void initModules() throws NoSuchMethodException, InvocationTargetE
if (!deprecatedModules.isEmpty()) {
LeafConfig.LOGGER.warn("The following enabled module(s) has been deprecated: {}, please proceed with caution!", formatModules(deprecatedModules));
}
+
+ WORLD_MODULES = List.copyOf(worldModules);
}
private static List formatModules(List modules) {
@@ -85,6 +95,18 @@ private static List getAnnotatedStaticFields(Class> clazz, Class exte
public static void clearModules() {
LOADED_MODULES.clear();
+ WORLD_MODULES = List.of();
+ }
+
+ /** Instantiates the cached, stateless world modules for one world configuration. */
+ public static void loadWorldModules(LeafWorldConfig config) {
+ try {
+ for (Class extends WorldConfigModule> moduleClass : WORLD_MODULES) {
+ moduleClass.getConstructor().newInstance().loadWorldConfig(config);
+ }
+ } catch (ReflectiveOperationException exception) {
+ throw new RuntimeException("Could not load Leaf world configuration modules", exception);
+ }
}
public abstract void onLoaded();
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigPathMigration.java b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigPathMigration.java
new file mode 100644
index 0000000000..6d79fe9ece
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigPathMigration.java
@@ -0,0 +1,38 @@
+package org.dreeam.leaf.config;
+
+import io.github.thatsmusic99.configurationmaster.api.ConfigSection;
+
+import java.util.Objects;
+
+/**
+ * Moves a configuration value from a legacy path to its replacement path.
+ */
+public final class ConfigPathMigration {
+
+ private ConfigPathMigration() {
+ }
+
+ /**
+ * Moves the value at {@code oldPath} to {@code newPath} when the old path exists.
+ *
+ * A successful migration removes the old path, so it is performed only once when the
+ * configuration is next saved.
+ *
+ * @param config the configuration containing both paths
+ * @param oldPath the deprecated configuration path
+ * @param newPath the replacement configuration path
+ * @return {@code true} if a value was moved
+ */
+ public static boolean migrate(ConfigSection config, String oldPath, String newPath) {
+ Objects.requireNonNull(config, "config");
+ if (oldPath.equals(newPath)) {
+ throw new IllegalArgumentException("The old and new config paths must differ");
+ }
+
+ if (!config.contains(oldPath)) {
+ return false;
+ }
+ config.moveTo(oldPath, newPath);
+ return true;
+ }
+}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java
index 809291531f..5db0e40c12 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java
@@ -46,7 +46,7 @@ public class LeafConfig {
public static final Logger LOGGER = LogManager.getLogger(LeafConfig.class.getSimpleName());
- protected static final String CURRENT_CONFIG_VERSION = "3.0";
+ public static final String CURRENT_CONFIG_VERSION = "3.0";
// It will be in uppercase by default, just make sure
private static final String REGION_COUNTRY_CODE = Locale.getDefault().getCountry().toUpperCase(Locale.ROOT);
private static final boolean IS_CHINESE_LOCALE = REGION_COUNTRY_CODE.equals("CN");
@@ -54,17 +54,16 @@ public class LeafConfig {
protected static final File CONFIG_DIRECTORY = new File("config");
protected static final String CONFIG_MODULE_PACKAGE = "org.dreeam.leaf.config.modules";
protected static final String GLOBAL_CONFIG_FILE = "leaf-global.yml";
- protected static final String DEFAULT_WORLD_CONFIG_FILE = "leaf-world-defaults.yml"; // Leaf TODO - Per world config
+ protected static final String DEFAULT_WORLD_CONFIG_FILE = "leaf-world-defaults.yml";
+ protected static final String WORLD_CONFIG_FILE = "leaf-world.yml";
private static final String SPARK_EXTRA_CONFIG_PROPERTY = "spark.serverconfigs.extra";
private static final String SPARK_HIDDEN_PATHS_PROPERTY = "spark.serverconfigs.hiddenpaths";
private static LeafGlobalConfig globalConfig;
+ private static LeafWorldConfig worldDefaultsConfig;
- //private static int preMajorVer;
- private static int preMinorVer;
- //private static int currMajorVer;
- private static int currMinorVer;
+ private static ConfigVersion previousConfigVersion = ConfigVersion.initial();
/* Load & Reload */
@@ -112,12 +111,39 @@ private static void loadConfig(boolean init) throws Exception {
// Load config modules
ConfigModule.initModules();
+
+ File worldDefaultsFile = new File(CONFIG_DIRECTORY, DEFAULT_WORLD_CONFIG_FILE);
+ if (!worldDefaultsFile.exists()) {
+ globalConfig.saveConfig();
+ Files.copy(new File(CONFIG_DIRECTORY, GLOBAL_CONFIG_FILE).toPath(), worldDefaultsFile.toPath());
+ }
+ worldDefaultsConfig = LeafWorldConfig.loadDefaults(worldDefaultsFile);
+ worldDefaultsConfig.saveConfig();
}
public static LeafGlobalConfig globalConfig() {
return globalConfig;
}
+ public static LeafWorldConfig worldDefaultsConfig() {
+ return worldDefaultsConfig;
+ }
+
+ /**
+ * Loads an explicit world override without creating a file when the world uses the defaults.
+ */
+ public static LeafWorldConfig createWorldConfig(Path worldDirectory) {
+ File worldConfigFile = worldDirectory.resolve(WORLD_CONFIG_FILE).toFile();
+ if (!LeafWorldConfig.exists(worldConfigFile)) {
+ return worldDefaultsConfig;
+ }
+ try {
+ return new LeafWorldConfig(worldConfigFile, worldDefaultsConfig);
+ } catch (Exception exception) {
+ throw new RuntimeException("Could not load Leaf world config for " + worldDirectory, exception);
+ }
+ }
+
static boolean isChineseLocale() {
return IS_CHINESE_LOCALE;
}
@@ -217,14 +243,91 @@ private static void findClassesInPackageByJar(String packageName, EnumerationUse this when migrating a renamed option path, before the current config version is
+ * persisted. For example, {@code isConfigVersionBefore("3.1")} is {@code true} for a
+ * configuration last written by Leaf 3.0.
+ *
+ * @param version a numeric dot-separated config version, such as {@code 3.1}
+ * @return {@code true} when the loaded configuration is older than {@code version}
+ * @throws IllegalArgumentException if {@code version} is not a numeric dot-separated version
+ */
+ public static boolean isConfigVersionBefore(String version) {
+ return previousConfigVersion.compareTo(ConfigVersion.parse(version)) < 0;
+ }
+ /**
+ * Returns whether the configuration being loaded is at least {@code version}.
+ *
+ * @param version a numeric dot-separated config version, such as {@code 3.1}
+ * @return {@code true} when the loaded configuration is not older than {@code version}
+ * @throws IllegalArgumentException if {@code version} is not a numeric dot-separated version
+ */
+ public static boolean isConfigVersionAtLeast(String version) {
+ return !isConfigVersionBefore(version);
+ }
+
+ private record ConfigVersion(List components) implements Comparable {
+
+ private static ConfigVersion initial() {
+ return new ConfigVersion(List.of(0));
+ }
+
+ private static ConfigVersion parse(String version) {
+ if (version == null || version.isBlank()) {
+ throw new IllegalArgumentException("Config version must not be blank");
+ }
+
+ String[] parts = version.split("\\.", -1);
+ List components = new ArrayList<>(parts.length);
+ for (String part : parts) {
+ if (part.isEmpty() || !part.chars().allMatch(Character::isDigit)) {
+ throw new IllegalArgumentException("Invalid config version: " + version);
+ }
+ try {
+ components.add(Integer.parseInt(part));
+ } catch (NumberFormatException exception) {
+ throw new IllegalArgumentException("Invalid config version: " + version, exception);
+ }
+ }
+ return new ConfigVersion(List.copyOf(components));
+ }
+
+ @Override
+ public int compareTo(ConfigVersion other) {
+ int componentCount = Math.max(this.components.size(), other.components.size());
+ for (int index = 0; index < componentCount; index++) {
+ int thisComponent = index < this.components.size() ? this.components.get(index) : 0;
+ int otherComponent = index < other.components.size() ? other.components.get(index) : 0;
+ int comparison = Integer.compare(thisComponent, otherComponent);
+ if (comparison != 0) {
+ return comparison;
+ }
+ }
+ return 0;
}
}
@@ -233,6 +336,7 @@ public static void loadConfigVersion(String preVer, String currVer) {
private static List buildSparkExtraConfigs() {
List extraConfigs = new ArrayList<>(Arrays.asList(
"config/leaf-global.yml",
+ "config/leaf-world-defaults.yml",
"config/gale-global.yml",
"config/gale-world-defaults.yml"
));
@@ -249,6 +353,10 @@ private static List buildSparkExtraConfigs() {
for (World world : Bukkit.getWorlds()) {
Path galeWorldFolder = world.getWorldFolder().toPath().resolve("gale-world.yml");
extraConfigs.add(galeWorldFolder.toString().replace("\\", "/").replace("./", "")); // Gale world config
+ Path leafWorldFile = world.getWorldFolder().toPath().resolve(WORLD_CONFIG_FILE);
+ if (Files.isRegularFile(leafWorldFile)) {
+ extraConfigs.add(leafWorldFile.toString().replace("\\", "/").replace("./", ""));
+ }
}
return extraConfigs;
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafGlobalConfig.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafGlobalConfig.java
index 201113ce09..c9ff528323 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafGlobalConfig.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafGlobalConfig.java
@@ -10,12 +10,18 @@
public class LeafGlobalConfig {
- private static ConfigFile configFile;
+ protected final ConfigFile configFile;
public LeafGlobalConfig(boolean init) throws Exception {
- configFile = ConfigFile.loadConfig(new File(LeafConfig.CONFIG_DIRECTORY, LeafConfig.GLOBAL_CONFIG_FILE));
+ this(new File(LeafConfig.CONFIG_DIRECTORY, LeafConfig.GLOBAL_CONFIG_FILE), true);
+ }
+
+ protected LeafGlobalConfig(File file, boolean loadConfigVersion) throws Exception {
+ configFile = ConfigFile.loadConfig(file);
- LeafConfig.loadPreviousConfigVersion(getString("config-version"));
+ if (loadConfigVersion) {
+ LeafConfig.loadPreviousConfigVersion(getString("config-version"));
+ }
configFile.set("config-version", LeafConfig.CURRENT_CONFIG_VERSION);
configFile.addComments("config-version", pickStringRegionBased("""
@@ -47,6 +53,15 @@ public void saveConfig() throws Exception {
configFile.save();
}
+ /**
+ * Moves a deprecated option path to its replacement.
+ *
+ * @see ConfigPathMigration#migrate(ConfigSection, String, String)
+ */
+ public boolean migratePath(String oldPath, String newPath) {
+ return ConfigPathMigration.migrate(configFile, oldPath, newPath);
+ }
+
// Config Utilities
/* getAndSet */
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafWorldConfig.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafWorldConfig.java
new file mode 100644
index 0000000000..5fdcab25b4
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafWorldConfig.java
@@ -0,0 +1,152 @@
+package org.dreeam.leaf.config;
+
+import io.github.thatsmusic99.configurationmaster.api.ConfigSection;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An optional world-level overlay for {@link LeafConfig#worldDefaultsConfig()}.
+ *
+ * The file is never created by this class. Callers must check {@link #exists()} before
+ * constructing it, so worlds without {@code leaf-world.yml} use the shared defaults directly.
+ */
+public final class LeafWorldConfig extends LeafGlobalConfig {
+
+ private final LeafGlobalConfig defaults;
+ public boolean secureSeedEnabled;
+
+ public static LeafWorldConfig loadDefaults(File file) throws Exception {
+ return new LeafWorldConfig(file, null);
+ }
+
+ public LeafWorldConfig(File file, LeafGlobalConfig defaults) throws Exception {
+ super(file, false);
+ this.defaults = defaults;
+ ConfigModule.loadWorldModules(this);
+ }
+
+ public static boolean exists(File file) {
+ return file.isFile();
+ }
+
+ @Override
+ protected void structureConfig() {
+ // World files are override-only and must not be populated with the defaults structure.
+ }
+
+ private boolean overrides(String path) {
+ return this.defaults == null || this.configFile.contains(path);
+ }
+
+ public boolean isDefaultsConfig() {
+ return this.defaults == null;
+ }
+
+ @Override
+ public boolean getBoolean(String path, boolean def, String comment) {
+ return overrides(path) ? super.getBoolean(path, def, comment) : defaults.getBoolean(path, def, comment);
+ }
+
+ @Override
+ public boolean getBoolean(String path, boolean def) {
+ return overrides(path) ? super.getBoolean(path, def) : defaults.getBoolean(path, def);
+ }
+
+ @Override
+ public String getString(String path, String def, String comment) {
+ return overrides(path) ? super.getString(path, def, comment) : defaults.getString(path, def, comment);
+ }
+
+ @Override
+ public String getString(String path, String def) {
+ return overrides(path) ? super.getString(path, def) : defaults.getString(path, def);
+ }
+
+ @Override
+ public double getDouble(String path, double def, String comment) {
+ return overrides(path) ? super.getDouble(path, def, comment) : defaults.getDouble(path, def, comment);
+ }
+
+ @Override
+ public double getDouble(String path, double def) {
+ return overrides(path) ? super.getDouble(path, def) : defaults.getDouble(path, def);
+ }
+
+ @Override
+ public int getInt(String path, int def, String comment) {
+ return overrides(path) ? super.getInt(path, def, comment) : defaults.getInt(path, def, comment);
+ }
+
+ @Override
+ public int getInt(String path, int def) {
+ return overrides(path) ? super.getInt(path, def) : defaults.getInt(path, def);
+ }
+
+ @Override
+ public long getLong(String path, long def, String comment) {
+ return overrides(path) ? super.getLong(path, def, comment) : defaults.getLong(path, def, comment);
+ }
+
+ @Override
+ public long getLong(String path, long def) {
+ return overrides(path) ? super.getLong(path, def) : defaults.getLong(path, def);
+ }
+
+ @Override
+ public List getList(String path, List def, String comment) {
+ return overrides(path) ? super.getList(path, def, comment) : defaults.getList(path, def, comment);
+ }
+
+ @Override
+ public List getList(String path, List def) {
+ return overrides(path) ? super.getList(path, def) : defaults.getList(path, def);
+ }
+
+ @Override
+ public ConfigSection getConfigSection(String path, Map values, String comment) {
+ return overrides(path) ? super.getConfigSection(path, values, comment) : defaults.getConfigSection(path, values, comment);
+ }
+
+ @Override
+ public ConfigSection getConfigSection(String path, Map values) {
+ return overrides(path) ? super.getConfigSection(path, values) : defaults.getConfigSection(path, values);
+ }
+
+ @Override
+ public Boolean getBoolean(String path) {
+ return overrides(path) ? super.getBoolean(path) : defaults.getBoolean(path);
+ }
+
+ @Override
+ public String getString(String path) {
+ return overrides(path) ? super.getString(path) : defaults.getString(path);
+ }
+
+ @Override
+ public Double getDouble(String path) {
+ return overrides(path) ? super.getDouble(path) : defaults.getDouble(path);
+ }
+
+ @Override
+ public Integer getInt(String path) {
+ return overrides(path) ? super.getInt(path) : defaults.getInt(path);
+ }
+
+ @Override
+ public Long getLong(String path) {
+ return overrides(path) ? super.getLong(path) : defaults.getLong(path);
+ }
+
+ @Override
+ public List getList(String path) {
+ return overrides(path) ? super.getList(path) : defaults.getList(path);
+ }
+
+ @Override
+ public ConfigSection getConfigSection(String path) {
+ return overrides(path) ? super.getConfigSection(path) : defaults.getConfigSection(path);
+ }
+
+}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/WorldConfigModule.java b/leaf-server/src/main/java/org/dreeam/leaf/config/WorldConfigModule.java
new file mode 100644
index 0000000000..09c61b6ce9
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/WorldConfigModule.java
@@ -0,0 +1,7 @@
+package org.dreeam.leaf.config;
+
+/** Loads a module's effective values from a world-defaults/override configuration view. */
+public interface WorldConfigModule {
+
+ void loadWorldConfig(LeafWorldConfig config);
+}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/SecureSeed.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/SecureSeed.java
index f06da7be8c..07fb9e6d8d 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/SecureSeed.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/SecureSeed.java
@@ -1,24 +1,39 @@
package org.dreeam.leaf.config.modules.misc;
import org.dreeam.leaf.config.ConfigModule;
-import org.dreeam.leaf.config.ConfigCategory;
+import org.dreeam.leaf.config.LeafConfig;
+import org.dreeam.leaf.config.LeafWorldConfig;
+import org.dreeam.leaf.config.WorldConfigModule;
-public class SecureSeed extends ConfigModule {
+public class SecureSeed extends ConfigModule implements WorldConfigModule {
- public String basePath() {
- return ConfigCategory.MISC.basePath() + ".secure-seed";
+ /**
+ * Keeps the module discoverable by the existing global module loader. The option itself is
+ * world-scoped and is loaded by {@link #loadWorldConfig(LeafWorldConfig)}.
+ */
+ public SecureSeed() {
}
- public static boolean enabled = false;
+ @Override
+ public void loadWorldConfig(LeafWorldConfig config) {
+ String path = "misc.secure-seed";
+ if (config.isDefaultsConfig()) {
+ config.addCommentRegionBased(path, """
+ Once you enable secure seed, all ores and structures are generated with a 1024-bit seed
+ instead of vanilla's 64-bit seed, making seed cracking impossible.""", """
+ 安全种子开启后,所有矿物与结构都将使用 1024 位种子,而非原版的 64 位种子,
+ 从而无法被破解。""");
+ }
+ config.secureSeedEnabled = config.getBoolean(path + ".enabled", false);
+ }
@Override
public void onLoaded() {
- globalConfig.addCommentRegionBased(basePath(), """
- Once you enable secure seed, all ores and structures are generated with 1024-bit seed
- instead of using 64-bit seed in vanilla, made seed cracker become impossible.""",
- """
- 安全种子开启后, 所有矿物与结构都将使用1024位的种子进行生成, 无法被破解.""");
+ // Secure Seed is registered by loadWorldConfig.
+ }
- enabled = globalConfig.getBoolean(basePath() + ".enabled", enabled);
+ /** Compatibility fallback for code that has no world context. */
+ public static boolean isEnabled() {
+ return LeafConfig.worldDefaultsConfig().secureSeedEnabled;
}
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/OptimizeNonFlushPacketSending.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/OptimizeNonFlushPacketSending.java
index f2f9821658..ff375bc7bc 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/OptimizeNonFlushPacketSending.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/OptimizeNonFlushPacketSending.java
@@ -30,4 +30,8 @@ public void onLoaded() {
需要重启服务器才能生效."""));
}
+
+ private void migrateConfigPath() {
+ globalConfig.migratePath(basePath() + ".OptimizeNonFlushPacketSending", basePath() + ".optimizeNonFlushPacketSending");
+ }
}
diff --git a/leaf-server/src/main/java/su/plo/matter/Globals.java b/leaf-server/src/main/java/su/plo/matter/Globals.java
index 11bf11174b..572c0f08dc 100644
--- a/leaf-server/src/main/java/su/plo/matter/Globals.java
+++ b/leaf-server/src/main/java/su/plo/matter/Globals.java
@@ -37,7 +37,7 @@ public enum Salt {
}
public static void setupGlobals(ServerLevel world) {
- if (!org.dreeam.leaf.config.modules.misc.SecureSeed.enabled) return;
+ if (!world.leafConfig().secureSeedEnabled) return;
long[] seed = world.getServer().getWorldGenSettings().options().featureSeed();
System.arraycopy(seed, 0, worldSeed, 0, WORLD_SEED_LONGS);
From f35a7685f886555dfe062a7ba3a8e04a1958768f Mon Sep 17 00:00:00 2001
From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com>
Date: Thu, 30 Jul 2026 00:34:42 +0800
Subject: [PATCH 02/14] work
---
.gitignore | 2 +
AGENTS.md | 268 ++++++++++++++++++
.../features/0004-Leaf-config.patch | 69 ++++-
.../0011-Move-random-tick-random.patch | 8 +-
...timize-random-calls-in-chunk-ticking.patch | 4 +-
...017-Remove-lambda-from-ticking-guard.patch | 8 +-
.../0042-Reduce-array-allocations.patch | 10 +-
...block-destruction-packet-allocations.patch | 8 +-
...fferfish-Dynamic-Activation-of-Brain.patch | 4 +-
.../features/0100-Leaves-Replay-Mod-API.patch | 8 +-
.../features/0103-Reduce-canSee-work.patch | 4 +-
.../features/0119-Matter-Secure-Seed.patch | 4 +-
.../0121-Faster-random-generator.patch | 10 +-
...e-stream-in-CraftWorld-spawnParticle.patch | 6 +-
.../features/0174-Cache-chunk-key.patch | 6 +-
...117075-Block-Entities-Unload-Lag-Spi.patch | 6 +-
.../features/0184-optimize-mob-despawn.patch | 8 +-
...-SparklyPaper-Parallel-world-ticking.patch | 38 +--
...celled-Projectile-Events-still-consu.patch | 4 +-
.../0197-Use-BFS-on-getSlopeDistance.patch | 4 +-
...00-Raytrace-AntiXray-SDK-integration.patch | 4 +-
.../features/0227-optimize-mob-spawning.patch | 4 +-
.../features/0234-optimize-random-tick.patch | 4 +-
.../features/0239-Paw-optimization.patch | 6 +-
...Paper-PR-Optimise-temptation-lookups.patch | 6 +-
...-Optimise-temptation-lookups-changes.patch | 4 +-
.../0255-thread-unsafe-chunk-map.patch | 4 +-
.../features/0257-optimize-get-chunk.patch | 4 +-
...0258-remove-shouldTickBlocksAt-check.patch | 4 +-
.../0262-optimize-fluid-state-access.patch | 4 +-
.../features/0267-cache-collision-list.patch | 4 +-
.../features/0268-fast-bit-radix-sort.patch | 4 +-
...Pluto-Expose-Direction-Plane-s-faces.patch | 8 +-
.../features/0275-Multithreaded-Tracker.patch | 4 +-
.../0277-Rewrite-entity-despawn-time.patch | 8 +-
.../features/0279-Cache-world-border.patch | 4 +-
...andomTick-new-BlockPos-instance-crea.patch | 6 +-
...onfigurable-ice-and-snow-tick-chance.patch | 4 +-
.../0302-disable-world-data-saving.patch | 6 +-
...Leaves-Lithium-Sleeping-Block-Entity.patch | 8 +-
...Leaves-Lithium-Sleeping-Block-Entity.patch | 4 +-
...309-Add-read-only-mode-for-Linear-v2.patch | 6 +-
.../org/dreeam/leaf/config/ConfigBinder.java | 171 +++++++++++
.../dreeam/leaf/config/ConfigCategory.java | 9 +-
.../org/dreeam/leaf/config/ConfigModule.java | 117 ++------
.../leaf/config/ConfigModuleLoader.java | 142 ++++++++++
.../org/dreeam/leaf/config/LeafConfig.java | 4 +-
.../leaf/config/LeafConfigAccessor.java | 177 ++++++++++++
.../dreeam/leaf/config/LeafGlobalConfig.java | 202 +------------
.../dreeam/leaf/config/LeafWorldConfig.java | 110 ++++---
.../dreeam/leaf/config/WorldConfigModule.java | 11 +-
.../config/annotations/ConfigClassInfo.java | 23 ++
.../leaf/config/annotations/ConfigInfo.java | 19 ++
.../leaf/config/annotations/DoNotLoad.java | 8 +
.../annotations/HotReloadUnsupported.java | 6 +
55 files changed, 1121 insertions(+), 467 deletions(-)
create mode 100644 AGENTS.md
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/ConfigBinder.java
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModuleLoader.java
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigAccessor.java
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/annotations/ConfigClassInfo.java
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/annotations/ConfigInfo.java
diff --git a/.gitignore b/.gitignore
index 811e261460..e43a3887b4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,3 +28,5 @@ leaf-server/build.gradle.kts
leaf-server/src/minecraft
paper-api
paper-server
+
+.codegraph
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000000..807794e391
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,268 @@
+# AGENTS.md
+
+## Project overview
+
+Leaf is a high-performance Paper fork.
+
+For tasks performed in this repository, work only on the applied source tree.
+The repository owner will manually inspect, test, and convert changes into
+patches when necessary.
+
+## Allowed scope
+
+Unless the user explicitly expands the scope, only modify applied Java source
+files in these areas:
+
+- Minecraft sources under `leaf-server`
+- Applied Paper API sources
+- Applied Paper server sources
+- Existing Leaf source files located alongside those applied sources
+
+Before editing, locate the actual applied source file in the current working
+tree. Follow the existing directory layout instead of assuming a source path.
+
+Files outside these applied source areas are read-only unless the user
+explicitly requests otherwise.
+
+## Patch restrictions
+
+Do not create, modify, delete, rename, regenerate, or reformat patch files.
+
+This includes, but is not limited to:
+
+- Paper patches
+- Minecraft patches
+- API patches
+- Leaf patches
+- Upstream patches
+- Generated `.patch` files
+- Patch metadata
+- Patch ordering or series files
+
+Do not run tasks or scripts that apply, rebuild, regenerate, reset, export, or
+otherwise modify patches.
+
+In particular, do not:
+
+- run patch rebuild tasks;
+- run patch generation tasks;
+- edit `.patch` files directly;
+- convert source changes into patches;
+- update an existing patch to include source changes;
+- reset applied sources from patches;
+- update Paper or Minecraft upstream references.
+
+Applied source files are the editing target for the current task, even when
+patch files are the canonical persisted form used by the project.
+
+## CodeGraph usage
+
+CodeGraph is available for repository-wide code navigation and dependency
+analysis.
+
+Use CodeGraph when the task requires understanding relationships across
+multiple files, including:
+
+- callers and callees;
+- interface implementations;
+- class inheritance;
+- field reads and writes;
+- method overrides;
+- dependency paths;
+- cross-module relationships between Minecraft, Paper API, Paper server, and
+ Leaf code;
+- ownership, lifecycle, or threading relationships that are not clear from the
+ current file.
+
+Prefer direct source inspection and text search for simple, local changes.
+Do not use CodeGraph when reading the current file and its immediate references
+is sufficient.
+
+CodeGraph is an index and may be incomplete or stale. Treat its results as
+navigation hints rather than authoritative source code.
+
+Before editing:
+
+1. Use CodeGraph to identify relevant symbols and relationships when the task
+ crosses files or modules.
+2. Open and inspect the actual applied source files returned by the query.
+3. Verify important callers, overrides, signatures, and control flow against
+ the current working tree.
+4. Search the source directly when CodeGraph returns no result or conflicts
+ with the checked-out code.
+
+Only analyze applied source code in the allowed scope:
+
+- Minecraft sources under `leaf-server`;
+- applied Paper API sources;
+- applied Paper server sources;
+- existing Leaf source files alongside those sources.
+
+Do not use CodeGraph results as a reason to modify patch files, generated
+patches, upstream metadata, or files outside the allowed editing scope.
+
+Do not update, rebuild, or reconfigure the CodeGraph index unless the user
+explicitly requests it.
+
+In the completion report, mention CodeGraph only when its analysis materially
+affected the change or when the index appeared incomplete or stale.
+
+## Editing workflow
+
+1. Read the relevant applied source and its surrounding implementation.
+2. For cross-file or cross-module behavior, use CodeGraph to locate callers,
+ callees, implementations, overrides, and state access.
+3. Verify relevant CodeGraph results against the actual checked-out source.
+4. Identify ownership, lifecycle, and threading assumptions when they affect
+ the requested change.
+5. Modify only the applied source files required for the task.
+6. Keep the diff focused and avoid unrelated formatting or cleanup.
+7. Review the resulting source diff for correctness.
+8. Report the changed files and any assumptions or risks.
+
+Stop after modifying and reviewing the applied source. Leave patch creation,
+patch rebuilding, compilation, testing, benchmarking, and runtime validation
+to the repository owner.
+
+## Validation policy
+
+The repository owner manually validates changes.
+
+Do not run:
+
+- Gradle build or compilation tasks;
+- test suites;
+- JMH benchmarks;
+- patch validation or rebuild tasks;
+- server startup tasks;
+- formatters that modify files;
+- scripts that generate or rewrite repository content.
+
+Read-only inspection commands are allowed, including:
+
+- locating source files;
+- searching references and call sites;
+- reading source and configuration;
+- inspecting `git status`;
+- inspecting diffs;
+- viewing Gradle files to understand dependencies or source layout.
+
+Do not claim that a change compiles, passes tests, improves performance, or
+works at runtime unless the user provides corresponding verification results.
+
+## Java conventions
+
+- Use the Java version and language style already established by the project.
+- Follow the style of the surrounding Minecraft, Paper, or Leaf code.
+- Prefer minimal and locally consistent changes.
+- Preserve nullability, visibility, annotations, and API contracts.
+- Avoid introducing new dependencies.
+- Avoid unrelated refactors unless they are necessary for the requested change.
+- Do not reformat surrounding code merely to match personal preferences.
+- Preserve comments that explain upstream behavior or non-obvious invariants.
+
+## Performance-sensitive code
+
+Leaf contains performance-sensitive server code. When changing a hot path:
+
+- avoid unnecessary allocation, boxing, copying, and temporary collections;
+- avoid streams and capturing lambdas when surrounding code uses explicit loops
+ for performance;
+- avoid repeated object construction for coordinates, positions, or keys;
+- consider sparse, typical, and dense workloads;
+- consider memory retention and backing-array capacity;
+- preserve early exits and established fast paths;
+- distinguish measured improvements from speculative micro-optimizations;
+- do not change observable vanilla or Paper behavior solely for performance.
+
+When proposing a performance optimization, explain its expected effect without
+claiming benchmark results that were not measured.
+
+## Threading and lifecycle
+
+Do not assume that code is safe to run asynchronously.
+
+Before changing thread ownership or asynchronous behavior, inspect:
+
+- mutable state accessed by the code;
+- tick-thread or region-thread assumptions;
+- world and chunk lifecycle;
+- entity addition and removal;
+- shutdown and unload behavior;
+- synchronization and publication;
+- interaction with plugins and Paper APIs.
+
+Do not move work to another thread, introduce concurrency, or weaken an
+existing thread check unless the user explicitly requests it and the safety
+argument is clear.
+
+## Compatibility
+
+Preserve the following unless the user explicitly requests a behavioral change:
+
+- vanilla behavior;
+- Paper API behavior;
+- plugin compatibility;
+- serialized and persistent data formats;
+- world loading and upgrading behavior;
+- existing configuration defaults;
+- public and internal API contracts.
+
+For API changes, consider both the applied Paper API source and its server-side
+implementation, but modify only the files needed for the requested task.
+
+## Generated code
+
+Do not modify generated files unless the user explicitly identifies the
+generated file as the desired editing target.
+
+If a source file appears to be generated, copied, or overwritten by a build or
+patch task, report that fact before relying on the change as persistent.
+
+Do not run a generator to update it.
+
+## Git safety
+
+Preserve all unrelated working-tree changes.
+
+Before editing, inspect the relevant files and use `git status` when available.
+Do not assume existing changes were produced by Codex.
+
+Never run destructive or history-changing commands, including:
+
+- `git reset`;
+- `git checkout --`;
+- `git restore`;
+- `git clean`;
+- `git rebase`;
+- `git commit`;
+- `git push`.
+
+Do not discard, overwrite, stage, commit, or revert user changes unless the
+user explicitly requests that exact action.
+
+## Review expectations
+
+When reviewing or changing applied source, prioritize:
+
+1. behavioral correctness;
+2. vanilla and Paper compatibility;
+3. thread safety and lifecycle correctness;
+4. hot-path allocation and computational cost;
+5. memory retention;
+6. API compatibility;
+7. clarity of the resulting source diff.
+
+Separate confirmed defects from possible risks and optional optimizations.
+
+## Completion report
+
+At the end of a task, report:
+
+- which applied source files changed;
+- what behavior changed;
+- important threading, compatibility, or performance considerations;
+- anything that still requires manual verification.
+
+Do not report patch files, generated patches, build results, test results, or
+benchmark results unless the user separately supplied or requested them.
diff --git a/leaf-server/minecraft-patches/features/0004-Leaf-config.patch b/leaf-server/minecraft-patches/features/0004-Leaf-config.patch
index 64f55612e9..1cb2b6aefa 100644
--- a/leaf-server/minecraft-patches/features/0004-Leaf-config.patch
+++ b/leaf-server/minecraft-patches/features/0004-Leaf-config.patch
@@ -7,7 +7,7 @@ Leaf Config v3
including load config, backup old or outdated config, and add config to spark profiler automatically.
diff --git a/net/minecraft/server/Main.java b/net/minecraft/server/Main.java
-index 1862df44a3319e459b547cebffb16972674856d6..0db1ba9622245f83d54b3828518fcc7dfa7b13af 100644
+index 1862df44a3319e459b547cebffb16972674856d6..4d199a3c725f58a16986020bf14d74050dea8ee5 100644
--- a/net/minecraft/server/Main.java
+++ b/net/minecraft/server/Main.java
@@ -102,6 +102,8 @@ public class Main {
@@ -23,7 +23,7 @@ index 1862df44a3319e459b547cebffb16972674856d6..0db1ba9622245f83d54b3828518fcc7d
Bootstrap.bootStrap();
Bootstrap.validate();
Util.startTimerHackThread();
-+ org.dreeam.leaf.config.ConfigModules.loadAfterBootstrap(); // Leaf - Leaf config - post load
++ org.dreeam.leaf.config.ConfigModule.loadAfterBootstrap(); // Leaf - Leaf config - post load
Path settingsFile = Paths.get("server.properties");
DedicatedServerSettings settings = new DedicatedServerSettings(options); // CraftBukkit - CLI argument support
settings.forceSave();
@@ -39,3 +39,68 @@ index d4a341400a445c35e855851cb72823f6d8572aab..8f4cbfc0cad23487a3c5425295a8fac2
// Paper start - Add onboarding message for initial server start
if (io.papermc.paper.configuration.GlobalConfiguration.isFirstStart) {
LOGGER.info("*************************************************************************************");
+diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
+index 745ec876080331f27f3eabf466ff914d39c8d2c4..164e8a1699755d8e7e756b0f322eca199e371b81 100644
+--- a/net/minecraft/server/level/ServerLevel.java
++++ b/net/minecraft/server/level/ServerLevel.java
+@@ -635,7 +635,24 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+ savedDataStorage.set(io.papermc.paper.world.saveddata.PaperWorldPDC.TYPE, loadedWorldData.pdc() == null ? io.papermc.paper.world.saveddata.PaperWorldPDC.TYPE.constructor().get() : loadedWorldData.pdc());
+ final GameRules gameRules = new GameRules(server.getWorldData().enabledFeatures(), savedDataStorage.computeIfAbsent(net.minecraft.world.level.gamerules.GameRuleMap.TYPE));
+ this.gameRules = gameRules;
+- super(levelData, dimension, server.registryAccess(), levelStem.type(), false, isDebug, biomeZoomSeed, server.getMaxChainedNeighborUpdates(), loadedWorldData.bukkitName(), gen, biomeProvider, env, spigotConfig -> server.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(server.storageSource.getDimensionPath(dimension), dimension.identifier(), spigotConfig, server.registryAccess(), gameRules)), spigotConfig -> server.galeConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(server.storageSource.getDimensionPath(dimension), dimension.identifier(), spigotConfig, server.registryAccess(), gameRules)), executor); // Paper - create paper world configs // Paper - Anti-Xray - Pass executor // Gale - Gale configuration
++ super(
++ levelData,
++ dimension,
++ server.registryAccess(),
++ levelStem.type(),
++ false,
++ isDebug,
++ biomeZoomSeed,
++ server.getMaxChainedNeighborUpdates(),
++ loadedWorldData.bukkitName(),
++ gen,
++ biomeProvider,
++ env,
++ spigotConfig -> server.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(server.storageSource.getDimensionPath(dimension), dimension.identifier(), spigotConfig, server.registryAccess(), gameRules)), // Paper - create paper world configs
++ spigotConfig -> server.galeConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(server.storageSource.getDimensionPath(dimension), dimension.identifier(), spigotConfig, server.registryAccess(), gameRules)), // Gale - Gale configuration
++ spigotConfig -> org.dreeam.leaf.config.LeafConfig.createWorldConfig(server.storageSource.getDimensionPath(dimension)), // Leaf - per-world configuration
++ executor
++ );
+ this.weatherData = savedDataStorage.computeIfAbsent(WeatherData.TYPE);
+ this.weatherData.setLevel(this);
+ this.typeKey = typeKey;
+diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
+index 96eccf7045781e25c8e6f4c2a391cd7d44672bf7..2ddc5f0105aab50f24580609ad1ea6c441d51ef9 100644
+--- a/net/minecraft/world/level/Level.java
++++ b/net/minecraft/world/level/Level.java
+@@ -179,6 +179,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+ }
+ // Gale end - Gale configuration
+
++ // Leaf start - per-world configuration
++ private final org.dreeam.leaf.config.LeafWorldConfig leafConfig;
++ public org.dreeam.leaf.config.LeafWorldConfig leafConfig() {
++ return this.leafConfig;
++ }
++ // Leaf end - per-world configuration
++
+ public final org.purpurmc.purpur.PurpurWorldConfig purpurConfig; // Purpur - Purpur config files
+ public static @Nullable BlockPos lastPhysicsProblem; // Spigot
+ public final Map explosionDensityCache = new java.util.HashMap<>(); // Paper - Optimize explosions
+@@ -892,6 +899,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+ io.papermc.paper.configuration.WorldConfiguration> paperWorldConfigCreator, // Paper - create paper world config
+ java.util.function.Function galeWorldConfigCreator, // Gale - Gale configuration
++ java.util.function.Function leafWorldConfigCreator, // Leaf - per-world configuration
+ java.util.concurrent.Executor executor // Paper - Anti-Xray
+ ) {
+ // Paper start - getblock optimisations - cache world height/sections
+@@ -908,6 +917,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+ this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config
+ this.purpurConfig = new org.purpurmc.purpur.PurpurWorldConfig(bukkitName, environment, worldKey); // Purpur - Purpur config files
+ this.galeConfig = galeWorldConfigCreator.apply(this.spigotConfig); // Gale - Gale configuration
++ this.leafConfig = leafWorldConfigCreator.apply(this.spigotConfig); // Leaf - per-world configuration
+ this.playerBreedingCooldowns = this.getNewBreedingCooldownCache(); // Purpur - Add adjustable breeding cooldown to config
+ this.generator = generator;
+ this.world = new CraftWorld((ServerLevel) this, worldKey, biomeProvider, environment);
diff --git a/leaf-server/minecraft-patches/features/0011-Move-random-tick-random.patch b/leaf-server/minecraft-patches/features/0011-Move-random-tick-random.patch
index cbfbfd7dcb..8d7bc46246 100644
--- a/leaf-server/minecraft-patches/features/0011-Move-random-tick-random.patch
+++ b/leaf-server/minecraft-patches/features/0011-Move-random-tick-random.patch
@@ -19,10 +19,10 @@ require it to be initialized earlier. By moving it to the superclass, we
initialize it earlier, ensuring that it is available sooner.
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 745ec876080331f27f3eabf466ff914d39c8d2c4..d9f97558c650c7a45e25780ef274eb92d48d797e 100644
+index 164e8a1699755d8e7e756b0f322eca199e371b81..67b687dfde8a5fcc9e2223c91c36a25cc533f99e 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -979,8 +979,6 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -996,8 +996,6 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
}
// Paper start - optimise random ticking
@@ -32,10 +32,10 @@ index 745ec876080331f27f3eabf466ff914d39c8d2c4..d9f97558c650c7a45e25780ef274eb92
final LevelChunkSection[] sections = chunk.getSections();
final int minSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection((ServerLevel)(Object)this);
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 96eccf7045781e25c8e6f4c2a391cd7d44672bf7..6ccf3d9cf5e048602f2f9d73bdb5ed73e7ac5a5b 100644
+index 2ddc5f0105aab50f24580609ad1ea6c441d51ef9..f4b0cbccab1e24587f13f451abb00c9eb1f4bae8 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
-@@ -183,6 +183,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -190,6 +190,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
public static @Nullable BlockPos lastPhysicsProblem; // Spigot
public final Map explosionDensityCache = new java.util.HashMap<>(); // Paper - Optimize explosions
public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Faster redstone torch rapid clock removal; Move from Map in BlockRedstoneTorch to here
diff --git a/leaf-server/minecraft-patches/features/0012-Optimize-random-calls-in-chunk-ticking.patch b/leaf-server/minecraft-patches/features/0012-Optimize-random-calls-in-chunk-ticking.patch
index 10a0f57291..bfcd1c3b46 100644
--- a/leaf-server/minecraft-patches/features/0012-Optimize-random-calls-in-chunk-ticking.patch
+++ b/leaf-server/minecraft-patches/features/0012-Optimize-random-calls-in-chunk-ticking.patch
@@ -57,10 +57,10 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index d9f97558c650c7a45e25780ef274eb92d48d797e..d8eeba21a5392af3b0f943296c0f943c38d23fc3 100644
+index 67b687dfde8a5fcc9e2223c91c36a25cc533f99e..b37c53aa74577b2ae5705875199c65737ec35b11 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1059,7 +1059,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1076,7 +1076,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
int minZ = chunkPos.getMinBlockZ();
ProfilerFiller profiler = Profiler.get();
profiler.push("thunder");
diff --git a/leaf-server/minecraft-patches/features/0017-Remove-lambda-from-ticking-guard.patch b/leaf-server/minecraft-patches/features/0017-Remove-lambda-from-ticking-guard.patch
index 5f57bf5513..0b109016ef 100644
--- a/leaf-server/minecraft-patches/features/0017-Remove-lambda-from-ticking-guard.patch
+++ b/leaf-server/minecraft-patches/features/0017-Remove-lambda-from-ticking-guard.patch
@@ -33,10 +33,10 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index d8eeba21a5392af3b0f943296c0f943c38d23fc3..aa54ee4700d7a13439987dfaad3095faf5bbf19e 100644
+index b37c53aa74577b2ae5705875199c65737ec35b11..478a3b9ad4738e789b425ec57192fce111126dcd 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -919,7 +919,19 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -936,7 +936,19 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
}
profiler.push("tick");
@@ -58,10 +58,10 @@ index d8eeba21a5392af3b0f943296c0f943c38d23fc3..aa54ee4700d7a13439987dfaad3095fa
}
}
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 6ccf3d9cf5e048602f2f9d73bdb5ed73e7ac5a5b..7ef63a242b0d32f6f6d1a95d7331f36921fbd029 100644
+index f4b0cbccab1e24587f13f451abb00c9eb1f4bae8..499a79d5592a6ff5bf74ca78dd3a7d70809d195d 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
-@@ -1582,10 +1582,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1592,10 +1592,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
final String msg = String.format("Entity threw exception at %s:%s,%s,%s", io.papermc.paper.util.MCUtil.getLevelName(entity.level()), entity.getX(), entity.getY(), entity.getZ());
MinecraftServer.LOGGER.error(msg, t);
getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerInternalException(msg, t))); // Paper - ServerExceptionEvent
diff --git a/leaf-server/minecraft-patches/features/0042-Reduce-array-allocations.patch b/leaf-server/minecraft-patches/features/0042-Reduce-array-allocations.patch
index a7f5376daf..05166f8359 100644
--- a/leaf-server/minecraft-patches/features/0042-Reduce-array-allocations.patch
+++ b/leaf-server/minecraft-patches/features/0042-Reduce-array-allocations.patch
@@ -153,10 +153,10 @@ index d7e5e8541fde613a5b82da59a1e2b95e0497fe61..bbcb768f587305223c67d30186d62e70
if (!itemStack.isEmpty()) {
slots.add(Pair.of(slot, itemStack.copy()));
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index aa54ee4700d7a13439987dfaad3095faf5bbf19e..b6b366d00ee97feb90c61908be0f1900048e2bab 100644
+index 478a3b9ad4738e789b425ec57192fce111126dcd..5881e6f68ad6108c35ec4b61c338b0f3572b366b 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1463,7 +1463,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1480,7 +1480,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
public static List getCurrentlyTickingEntities() {
Entity ticking = currentlyTickingEntity.get();
@@ -457,10 +457,10 @@ index 470104cec5a78ea4c68cce664590c9af96500829..10be004c98d608327c18fb07c3e9d114
// do not even check enchantments for item with lower or equal damage percent
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 7ef63a242b0d32f6f6d1a95d7331f36921fbd029..734a8fe92417aff18fb3964df2c90f46e054c86b 100644
+index 499a79d5592a6ff5bf74ca78dd3a7d70809d195d..d64c2837267958a1226b9216da587ca58770a600 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
-@@ -1923,7 +1923,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1933,7 +1933,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
public org.bukkit.entity.Entity[] getChunkEntities(int chunkX, int chunkZ) {
ca.spottedleaf.moonrise.patches.chunk_system.level.entity.ChunkEntitySlices slices = ((ServerLevel)this).moonrise$getEntityLookup().getChunk(chunkX, chunkZ);
if (slices == null) {
@@ -469,7 +469,7 @@ index 7ef63a242b0d32f6f6d1a95d7331f36921fbd029..734a8fe92417aff18fb3964df2c90f46
}
List ret = new java.util.ArrayList<>();
-@@ -1934,7 +1934,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1944,7 +1944,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
}
}
diff --git a/leaf-server/minecraft-patches/features/0060-Reduce-block-destruction-packet-allocations.patch b/leaf-server/minecraft-patches/features/0060-Reduce-block-destruction-packet-allocations.patch
index ea790b0d99..427e70db13 100644
--- a/leaf-server/minecraft-patches/features/0060-Reduce-block-destruction-packet-allocations.patch
+++ b/leaf-server/minecraft-patches/features/0060-Reduce-block-destruction-packet-allocations.patch
@@ -13,10 +13,10 @@ As part of: SportPaper (https://github.com/Electroid/SportPaper)
Licensed under: GPL-3.0-only (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index b6b366d00ee97feb90c61908be0f1900048e2bab..f45d9ade70508daffea4d02ea93c0eea0ce5e8cc 100644
+index 5881e6f68ad6108c35ec4b61c338b0f3572b366b..c0c370edcdd335bb3ea9c9142db3afa4465f5118 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1800,6 +1800,11 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1817,6 +1817,11 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@Override
public void destroyBlockProgress(final int id, final BlockPos blockPos, final int progress) {
@@ -28,7 +28,7 @@ index b6b366d00ee97feb90c61908be0f1900048e2bab..f45d9ade70508daffea4d02ea93c0eea
// CraftBukkit start
Player breakerPlayer = null;
Entity entity = this.getEntity(id);
-@@ -1816,7 +1821,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1833,7 +1838,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
.callEvent();
}
// Paper end - Add BlockBreakProgressUpdateEvent
@@ -37,7 +37,7 @@ index b6b366d00ee97feb90c61908be0f1900048e2bab..f45d9ade70508daffea4d02ea93c0eea
if (player.level() == this && player.getId() != id) {
double xd = blockPos.getX() - player.getX();
double yd = blockPos.getY() - player.getY();
-@@ -1827,7 +1832,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1844,7 +1849,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
continue;
}
// CraftBukkit end
diff --git a/leaf-server/minecraft-patches/features/0082-Pufferfish-Dynamic-Activation-of-Brain.patch b/leaf-server/minecraft-patches/features/0082-Pufferfish-Dynamic-Activation-of-Brain.patch
index d18df02d61..2e8bd5a36b 100644
--- a/leaf-server/minecraft-patches/features/0082-Pufferfish-Dynamic-Activation-of-Brain.patch
+++ b/leaf-server/minecraft-patches/features/0082-Pufferfish-Dynamic-Activation-of-Brain.patch
@@ -71,10 +71,10 @@ index 914762bbc1b538d9eaaa86f6975cb088a791b286..12aa35a4a07ec328cf6702e01a8e2f7b
}
}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index f45d9ade70508daffea4d02ea93c0eea0ce5e8cc..aa2355009abe871a6cb6e8c37543511e6ec47e54 100644
+index c0c370edcdd335bb3ea9c9142db3afa4465f5118..0f845830a7bb30561f44cafe867c9b5ac404fa43 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -903,6 +903,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -920,6 +920,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
this.entityTickList
.forEach(
entity -> {
diff --git a/leaf-server/minecraft-patches/features/0100-Leaves-Replay-Mod-API.patch b/leaf-server/minecraft-patches/features/0100-Leaves-Replay-Mod-API.patch
index fbe207d66b..fa8bd42e22 100644
--- a/leaf-server/minecraft-patches/features/0100-Leaves-Replay-Mod-API.patch
+++ b/leaf-server/minecraft-patches/features/0100-Leaves-Replay-Mod-API.patch
@@ -299,7 +299,7 @@ index 37c9f983ae89c497f44a1b7967d741abf909e0a0..d7fb2505f2a86ca0d81f144624ef2e1a
this.setListData(players);
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index aa2355009abe871a6cb6e8c37543511e6ec47e54..bc08326c6b8e0657357f57f9bc47ef3c55f7744f 100644
+index 0f845830a7bb30561f44cafe867c9b5ac404fa43..9328c7cf8b4c21e3405c9d86e2022dde6933cb8c 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -238,6 +238,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -310,7 +310,7 @@ index aa2355009abe871a6cb6e8c37543511e6ec47e54..bc08326c6b8e0657357f57f9bc47ef3c
@Override
public @Nullable LevelChunk getChunkIfLoaded(int x, int z) {
-@@ -760,6 +761,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -777,6 +778,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
this.chunkDataController = new ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller.ChunkDataController((ServerLevel)(Object)this, this.chunkTaskScheduler);
// Paper end - rewrite chunk system
this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit
@@ -318,7 +318,7 @@ index aa2355009abe871a6cb6e8c37543511e6ec47e54..bc08326c6b8e0657357f57f9bc47ef3c
}
// Paper start
-@@ -2946,6 +2948,11 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -2963,6 +2965,11 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
// ServerLevel.this.getChunkSource().addEntity(entity); // Paper - ignore and warn about illegal addEntity calls instead of crashing server; moved down below valid=true
if (entity instanceof ServerPlayer player) {
ServerLevel.this.players.add(player);
@@ -330,7 +330,7 @@ index aa2355009abe871a6cb6e8c37543511e6ec47e54..bc08326c6b8e0657357f57f9bc47ef3c
if (player.isReceivingWaypoints()) {
ServerLevel.this.getWaypointManager().addPlayer(player);
}
-@@ -3024,6 +3031,11 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -3041,6 +3048,11 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
ServerLevel.this.getChunkSource().removeEntity(entity);
if (entity instanceof ServerPlayer player) {
ServerLevel.this.players.remove(player);
diff --git a/leaf-server/minecraft-patches/features/0103-Reduce-canSee-work.patch b/leaf-server/minecraft-patches/features/0103-Reduce-canSee-work.patch
index 319ab67be5..d054eea247 100644
--- a/leaf-server/minecraft-patches/features/0103-Reduce-canSee-work.patch
+++ b/leaf-server/minecraft-patches/features/0103-Reduce-canSee-work.patch
@@ -7,10 +7,10 @@ Co-authored by: Martijn Muijsers
Co-authored by: MachineBreaker
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 734a8fe92417aff18fb3964df2c90f46e054c86b..e5b33ef26c971348b03f176d9b860bff79f8bc61 100644
+index d64c2837267958a1226b9216da587ca58770a600..3cb7f14b0f781dd93754f74ad3348d2bb5899c49 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
-@@ -957,17 +957,19 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -967,17 +967,19 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
for (int i = 0, len = entities.size(); i < len; ++i) {
Entity entity = entities.get(i);
diff --git a/leaf-server/minecraft-patches/features/0119-Matter-Secure-Seed.patch b/leaf-server/minecraft-patches/features/0119-Matter-Secure-Seed.patch
index cecfae7a42..c3dc2ffe2f 100644
--- a/leaf-server/minecraft-patches/features/0119-Matter-Secure-Seed.patch
+++ b/leaf-server/minecraft-patches/features/0119-Matter-Secure-Seed.patch
@@ -45,10 +45,10 @@ index 98f07bacb2a94b368c1969dc0cfb11b72f6cea1a..6755281b1dedf2078e1623918985de77
}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index bc08326c6b8e0657357f57f9bc47ef3c55f7744f..306ab28189d3272efbdaaf09444accf52f570fee 100644
+index 9328c7cf8b4c21e3405c9d86e2022dde6933cb8c..5be30f950fecce5e32b29e69dbb7faf3d54507a4 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -692,6 +692,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -709,6 +709,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
generator = new org.bukkit.craftbukkit.generator.CustomChunkGenerator(this, generator, gen);
}
// CraftBukkit end
diff --git a/leaf-server/minecraft-patches/features/0121-Faster-random-generator.patch b/leaf-server/minecraft-patches/features/0121-Faster-random-generator.patch
index b9a05f3eeb..1d48c9a1c5 100644
--- a/leaf-server/minecraft-patches/features/0121-Faster-random-generator.patch
+++ b/leaf-server/minecraft-patches/features/0121-Faster-random-generator.patch
@@ -27,10 +27,10 @@ index 6755281b1dedf2078e1623918985de772bb78dfe..aa30556730b3c924f72ccd8702a88c38
final ServerLevel world = this.level;
final int randomTickSpeed = world.getGameRules().get(GameRules.RANDOM_TICK_SPEED);
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 306ab28189d3272efbdaaf09444accf52f570fee..0817fd8c0006272f1ed0bce560f1200324ff42ec 100644
+index 5be30f950fecce5e32b29e69dbb7faf3d54507a4..729806478408d8f835520c7f50af97efee3d3597 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -998,7 +998,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1015,7 +1015,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
private void optimiseRandomTick(final LevelChunk chunk, final int tickSpeed) {
final LevelChunkSection[] sections = chunk.getSections();
final int minSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection((ServerLevel)(Object)this);
@@ -39,7 +39,7 @@ index 306ab28189d3272efbdaaf09444accf52f570fee..0817fd8c0006272f1ed0bce560f12003
final boolean doubleTickFluids = !ca.spottedleaf.moonrise.common.PlatformHooks.get().configFixMC224294();
final ChunkPos cpos = chunk.getPos();
-@@ -1045,7 +1045,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1062,7 +1062,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
// Paper end - optimise random ticking
public void tickChunk(final LevelChunk chunk, final int tickSpeed) {
@@ -110,7 +110,7 @@ index 7035d1ede6383c2016685cd2ca2f86e269f75c73..50c424b1d498d052bd27f37e855d0db3
private static final class RandomRandomSource extends ca.spottedleaf.moonrise.common.util.ThreadUnsafeRandom {
public RandomRandomSource() {
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index e5b33ef26c971348b03f176d9b860bff79f8bc61..94619316b2f2cc919b66c334177239948e76ac75 100644
+index 3cb7f14b0f781dd93754f74ad3348d2bb5899c49..067d01e6f03ad75fdb3cc8163815247a84cfae0d 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
@@ -132,7 +132,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
@@ -122,7 +122,7 @@ index e5b33ef26c971348b03f176d9b860bff79f8bc61..94619316b2f2cc919b66c33417723994
@Deprecated
private final RandomSource soundSeedGenerator = RandomSource.createThreadSafe();
private final Holder dimensionTypeRegistration;
-@@ -183,7 +183,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -190,7 +190,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
public static @Nullable BlockPos lastPhysicsProblem; // Spigot
public final Map explosionDensityCache = new java.util.HashMap<>(); // Paper - Optimize explosions
public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Faster redstone torch rapid clock removal; Move from Map in BlockRedstoneTorch to here
diff --git a/leaf-server/minecraft-patches/features/0141-Remove-stream-in-CraftWorld-spawnParticle.patch b/leaf-server/minecraft-patches/features/0141-Remove-stream-in-CraftWorld-spawnParticle.patch
index 3488d731a1..5f57eefbc9 100644
--- a/leaf-server/minecraft-patches/features/0141-Remove-stream-in-CraftWorld-spawnParticle.patch
+++ b/leaf-server/minecraft-patches/features/0141-Remove-stream-in-CraftWorld-spawnParticle.patch
@@ -5,10 +5,10 @@ Subject: [PATCH] Remove stream in CraftWorld#spawnParticle
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 0817fd8c0006272f1ed0bce560f1200324ff42ec..29e303e39d234a1f203c1aa5e182301f6a06143e 100644
+index 729806478408d8f835520c7f50af97efee3d3597..5044c5a947b59a821d3ce6cf3e1d55fd29d575da 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -2257,7 +2257,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -2274,7 +2274,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
for (int i = 0; i < receivers.size(); i++) { // Paper - particle API
ServerPlayer player = receivers.get(i); // Paper - particle API
@@ -17,7 +17,7 @@ index 0817fd8c0006272f1ed0bce560f1200324ff42ec..29e303e39d234a1f203c1aa5e182301f
if (this.sendParticles(player, overrideLimiter, x, y, z, packet)) {
result++;
}
-@@ -2266,6 +2266,44 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -2283,6 +2283,44 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
return result;
}
diff --git a/leaf-server/minecraft-patches/features/0174-Cache-chunk-key.patch b/leaf-server/minecraft-patches/features/0174-Cache-chunk-key.patch
index fdd77d2a83..2b06c1e594 100644
--- a/leaf-server/minecraft-patches/features/0174-Cache-chunk-key.patch
+++ b/leaf-server/minecraft-patches/features/0174-Cache-chunk-key.patch
@@ -119,7 +119,7 @@ index 63cc7970b9df5e84743084ca99650bc3d13b970e..022dd571623224fd87c4aa4a50ebdba4
// Paper end - rewrite chunk system
}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 29e303e39d234a1f203c1aa5e182301f6a06143e..9fd8e8ff0b61535ce639ba6c8ab388ee9bd68775 100644
+index 5044c5a947b59a821d3ce6cf3e1d55fd29d575da..6c5e50d7e66840a659264e69b3b7a9213f05a384 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -549,7 +549,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -131,7 +131,7 @@ index 29e303e39d234a1f203c1aa5e182301f6a06143e..9fd8e8ff0b61535ce639ba6c8ab388ee
return;
}
-@@ -2824,7 +2824,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -2841,7 +2841,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
public boolean areEntitiesActuallyLoadedAndTicking(final ChunkPos pos) {
// Paper start - rewrite chunk system
@@ -140,7 +140,7 @@ index 29e303e39d234a1f203c1aa5e182301f6a06143e..9fd8e8ff0b61535ce639ba6c8ab388ee
return chunkHolder != null && chunkHolder.isEntityTickingReady();
// Paper end - rewrite chunk system
}
-@@ -2844,7 +2844,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -2861,7 +2861,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
public boolean canSpawnEntitiesInChunk(final ChunkPos pos) {
// Paper start - rewrite chunk system
diff --git a/leaf-server/minecraft-patches/features/0176-Paper-PR-Fix-MC-117075-Block-Entities-Unload-Lag-Spi.patch b/leaf-server/minecraft-patches/features/0176-Paper-PR-Fix-MC-117075-Block-Entities-Unload-Lag-Spi.patch
index a87d85b785..4be12c2366 100644
--- a/leaf-server/minecraft-patches/features/0176-Paper-PR-Fix-MC-117075-Block-Entities-Unload-Lag-Spi.patch
+++ b/leaf-server/minecraft-patches/features/0176-Paper-PR-Fix-MC-117075-Block-Entities-Unload-Lag-Spi.patch
@@ -12,7 +12,7 @@ We replaced the `blockEntityTickers` list with a custom list based on fastutil's
This is WAY FASTER than using `removeAll` with a list of entries to be removed, because we don't need to calculate the identity of each block entity to be removed, and we can jump directly to where the search should begin, giving a performance boost for small removals (because we don't need to loop thru the entire list to find what element should be removed) and a performance boost for big removals (no need to calculate the identity of each block entity).
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 94619316b2f2cc919b66c334177239948e76ac75..786117f3fc54933454bf4916b19e2128e7581427 100644
+index 067d01e6f03ad75fdb3cc8163815247a84cfae0d..dbabd052dc01c8a5f12f3df95b40b7176d7a6a0a 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
@@ -119,7 +119,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
@@ -24,7 +24,7 @@ index 94619316b2f2cc919b66c334177239948e76ac75..786117f3fc54933454bf4916b19e2128
protected final CollectingNeighborUpdater neighborUpdater;
private final List pendingBlockEntityTickers = Lists.newArrayList();
private boolean tickingBlockEntities;
-@@ -1555,13 +1555,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1565,13 +1565,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
int tickedEntities = 0; // Paper - rewrite chunk system
// Paper start - Fix MC-117075 use removeAll
@@ -39,7 +39,7 @@ index 94619316b2f2cc919b66c334177239948e76ac75..786117f3fc54933454bf4916b19e2128
} else if (tickBlockEntities && this.shouldTickBlocksAt(ticker.getPos())) {
ticker.tick();
// Paper start - rewrite chunk system
-@@ -1572,7 +1570,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1582,7 +1580,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
}
}
diff --git a/leaf-server/minecraft-patches/features/0184-optimize-mob-despawn.patch b/leaf-server/minecraft-patches/features/0184-optimize-mob-despawn.patch
index 4a5b17c2cb..385477fd27 100644
--- a/leaf-server/minecraft-patches/features/0184-optimize-mob-despawn.patch
+++ b/leaf-server/minecraft-patches/features/0184-optimize-mob-despawn.patch
@@ -5,10 +5,10 @@ Subject: [PATCH] optimize mob despawn
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 9fd8e8ff0b61535ce639ba6c8ab388ee9bd68775..629faaf62709163b817783c67e40aa6f591d1a54 100644
+index 6c5e50d7e66840a659264e69b3b7a9213f05a384..e6ddeb1849cfb5173491d339cbb54d7b6a29dc07 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -903,6 +903,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -920,6 +920,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
}
io.papermc.paper.entity.activation.ActivationRange.activateEntities(this); // Paper - EAR
@@ -16,7 +16,7 @@ index 9fd8e8ff0b61535ce639ba6c8ab388ee9bd68775..629faaf62709163b817783c67e40aa6f
this.entityTickList
.forEach(
entity -> {
-@@ -910,7 +911,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -927,7 +928,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
if (!entity.isRemoved()) {
if (!tickRateManager.isEntityFrozen(entity)) {
profiler.push("checkDespawn");
@@ -25,7 +25,7 @@ index 9fd8e8ff0b61535ce639ba6c8ab388ee9bd68775..629faaf62709163b817783c67e40aa6f
profiler.pop();
if (true) { // Paper - rewrite chunk system
Entity vehicle = entity.getVehicle();
-@@ -1044,6 +1045,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1061,6 +1062,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
}
// Paper end - optimise random ticking
diff --git a/leaf-server/minecraft-patches/features/0192-SparklyPaper-Parallel-world-ticking.patch b/leaf-server/minecraft-patches/features/0192-SparklyPaper-Parallel-world-ticking.patch
index 7a24f70dca..5513fff4ea 100644
--- a/leaf-server/minecraft-patches/features/0192-SparklyPaper-Parallel-world-ticking.patch
+++ b/leaf-server/minecraft-patches/features/0192-SparklyPaper-Parallel-world-ticking.patch
@@ -365,7 +365,7 @@ index aa30556730b3c924f72ccd8702a88c380d42c6a9..d9d0a7693e86c244af47513ae1a3e39e
continue;
}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 629faaf62709163b817783c67e40aa6f591d1a54..07fc6636477fa0a5020234b970c3c2852fb2a8a7 100644
+index e6ddeb1849cfb5173491d339cbb54d7b6a29dc07..a03199d0e52e25dd766b61c7c5e1e6a75122f105 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -196,7 +196,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -389,7 +389,7 @@ index 629faaf62709163b817783c67e40aa6f591d1a54..07fc6636477fa0a5020234b970c3c285
// CraftBukkit start
private final ResourceKey typeKey;
-@@ -763,6 +768,15 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -780,6 +785,15 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
// Paper end - rewrite chunk system
this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit
this.realPlayers = Lists.newArrayList(); // Leaves - skip
@@ -405,7 +405,7 @@ index 629faaf62709163b817783c67e40aa6f591d1a54..07fc6636477fa0a5020234b970c3c285
}
// Paper start
-@@ -816,10 +830,147 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -833,10 +847,147 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
return previous;
}
@@ -553,7 +553,7 @@ index 629faaf62709163b817783c67e40aa6f591d1a54..07fc6636477fa0a5020234b970c3c285
TickRateManager tickRateManager = this.tickRateManager();
boolean runs = tickRateManager.runsNormally();
if (runs) {
-@@ -830,6 +981,12 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -847,6 +998,12 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
profiler.pop();
}
@@ -566,7 +566,7 @@ index 629faaf62709163b817783c67e40aa6f591d1a54..07fc6636477fa0a5020234b970c3c285
int percentage = this.getGameRules().get(GameRules.PLAYERS_SLEEPING_PERCENTAGE);
if (this.purpurConfig.playersSkipNight && this.sleepStatus.areEnoughSleeping(percentage) && this.sleepStatus.areEnoughDeepSleeping(percentage, this.players)) { // Purpur - Config for skipping night
Optional> defaultClock = this.dimensionType().defaultClock();
-@@ -935,6 +1092,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -952,6 +1109,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD);
// Paper end - Prevent block entity and entity crashes
}
@@ -574,7 +574,7 @@ index 629faaf62709163b817783c67e40aa6f591d1a54..07fc6636477fa0a5020234b970c3c285
this.moonrise$midTickTasks(); // Paper - rewrite chunk system
// Gale end - Airplane - remove lambda from ticking guard - copied from guardEntityTick
profiler.pop();
-@@ -1443,7 +1601,10 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1460,7 +1618,10 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
fluidState.tick(this, pos, blockState);
}
// Paper start - rewrite chunk system
@@ -586,7 +586,7 @@ index 629faaf62709163b817783c67e40aa6f591d1a54..07fc6636477fa0a5020234b970c3c285
((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks();
}
// Paper end - rewrite chunk system
-@@ -1456,7 +1617,10 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1473,7 +1634,10 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
state.tick(this, pos, this.random);
}
// Paper start - rewrite chunk system
@@ -598,7 +598,7 @@ index 629faaf62709163b817783c67e40aa6f591d1a54..07fc6636477fa0a5020234b970c3c285
((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks();
}
// Paper end - rewrite chunk system
-@@ -1708,6 +1872,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1725,6 +1889,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
}
private void addPlayer(final ServerPlayer player) {
@@ -606,7 +606,7 @@ index 629faaf62709163b817783c67e40aa6f591d1a54..07fc6636477fa0a5020234b970c3c285
Entity existing = this.getEntity(player.getUUID());
if (existing != null) {
LOGGER.warn("Force-added player with duplicate UUID {}", player.getUUID());
-@@ -1720,7 +1885,13 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1737,7 +1902,13 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
// CraftBukkit start
private boolean addEntity(final Entity entity, final org.bukkit.event.entity.CreatureSpawnEvent.@Nullable SpawnReason spawnReason) {
@@ -1098,18 +1098,18 @@ index 6e5438a327287981d7732659530fba6cd06b591e..8b1582e2f881c581bd6788c587b830ef
DataComponentPatch newPatch = this.components.asPatch();
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 786117f3fc54933454bf4916b19e2128e7581427..6f360efdd6248cef1cf3d1392eeae4d273c80a79 100644
+index dbabd052dc01c8a5f12f3df95b40b7176d7a6a0a..2c49a363d3d457a360e05d7f546eb63e09805dd7 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
-@@ -180,6 +180,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
- // Gale end - Gale configuration
+@@ -187,6 +187,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+ // Leaf end - per-world configuration
public final org.purpurmc.purpur.PurpurWorldConfig purpurConfig; // Purpur - Purpur config files
+ public final io.papermc.paper.redstone.RedstoneWireTurbo turbo; // Leaf - SparklyPaper - parallel world ticking - moved to world
public static @Nullable BlockPos lastPhysicsProblem; // Spigot
public final Map explosionDensityCache = new java.util.HashMap<>(); // Paper - Optimize explosions
public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Faster redstone torch rapid clock removal; Move from Map in BlockRedstoneTorch to here
-@@ -932,6 +933,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -942,6 +943,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
this.damageSources = new DamageSources(registryAccess);
this.entityLookup = new ca.spottedleaf.moonrise.patches.chunk_system.level.entity.dfl.DefaultEntityLookup(this); // Paper - rewrite chunk system
this.chunkPacketBlockController = this.paperConfig().anticheat.antiXray.enabled ? new io.papermc.paper.antixray.ChunkPacketBlockControllerAntiXray(this, executor) : io.papermc.paper.antixray.ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray
@@ -1117,7 +1117,7 @@ index 786117f3fc54933454bf4916b19e2128e7581427..6f360efdd6248cef1cf3d1392eeae4d2
}
public int getNextEntityId() {
-@@ -1113,6 +1115,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1123,6 +1125,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
@Override
public boolean setBlock(final BlockPos pos, final BlockState blockState, final @Block.UpdateFlags int updateFlags, final int updateLimit) {
@@ -1125,7 +1125,7 @@ index 786117f3fc54933454bf4916b19e2128e7581427..6f360efdd6248cef1cf3d1392eeae4d2
// CraftBukkit start - tree generation
if (this.captureTreeGeneration) {
// Paper start - Protect Bedrock and End Portal/Frames from being destroyed
-@@ -1563,7 +1566,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1573,7 +1576,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
} else if (tickBlockEntities && this.shouldTickBlocksAt(ticker.getPos())) {
ticker.tick();
// Paper start - rewrite chunk system
@@ -1137,7 +1137,7 @@ index 786117f3fc54933454bf4916b19e2128e7581427..6f360efdd6248cef1cf3d1392eeae4d2
((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)(Level)(Object)this).moonrise$midTickTasks();
}
// Paper end - rewrite chunk system
-@@ -1585,6 +1591,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1595,6 +1601,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); // Gale - Airplane - remove lambda from ticking guard - diff on change ServerLevel#tick
// Paper end - Prevent block entity and entity crashes
}
@@ -1145,7 +1145,7 @@ index 786117f3fc54933454bf4916b19e2128e7581427..6f360efdd6248cef1cf3d1392eeae4d2
this.moonrise$midTickTasks(); // Paper - rewrite chunk system // Gale - Airplane - remove lambda from ticking guard - diff on change ServerLevel#tick
}
-@@ -1736,6 +1743,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1746,6 +1753,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
@Override
public @Nullable BlockEntity getBlockEntity(final BlockPos pos) {
@@ -1153,7 +1153,7 @@ index 786117f3fc54933454bf4916b19e2128e7581427..6f360efdd6248cef1cf3d1392eeae4d2
// Paper start - Perf: Optimize capturedTileEntities lookup
net.minecraft.world.level.block.entity.BlockEntity blockEntity;
if (!this.capturedBlockEntities.isEmpty() && (blockEntity = this.capturedBlockEntities.get(pos)) != null) {
-@@ -1752,6 +1760,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1762,6 +1770,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
}
public void setBlockEntity(final BlockEntity blockEntity) {
@@ -1161,7 +1161,7 @@ index 786117f3fc54933454bf4916b19e2128e7581427..6f360efdd6248cef1cf3d1392eeae4d2
BlockPos pos = blockEntity.getBlockPos();
if (this.isInValidBounds(pos)) {
// CraftBukkit start
-@@ -1824,6 +1833,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1834,6 +1843,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
@Override
public List getEntities(final @Nullable Entity except, final AABB bb, final Predicate super Entity> selector) {
diff --git a/leaf-server/minecraft-patches/features/0194-Paper-PR-Fix-cancelled-Projectile-Events-still-consu.patch b/leaf-server/minecraft-patches/features/0194-Paper-PR-Fix-cancelled-Projectile-Events-still-consu.patch
index 6941ba1b11..e3e6056dbf 100644
--- a/leaf-server/minecraft-patches/features/0194-Paper-PR-Fix-cancelled-Projectile-Events-still-consu.patch
+++ b/leaf-server/minecraft-patches/features/0194-Paper-PR-Fix-cancelled-Projectile-Events-still-consu.patch
@@ -244,10 +244,10 @@ index 039fc3ced4e06a97346264b52375ddd298fb8422..e6e88258a67ecc5a098ad903221fdc0f
player.getInventory().removeItem(projectile);
}
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 6f360efdd6248cef1cf3d1392eeae4d273c80a79..909b22783a482c9d96f088f288305323e8487149 100644
+index 2c49a363d3d457a360e05d7f546eb63e09805dd7..8f934577eed411bdca128e5bd8de86d86be7238c 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
-@@ -185,6 +185,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -192,6 +192,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
public final Map explosionDensityCache = new java.util.HashMap<>(); // Paper - Optimize explosions
public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Faster redstone torch rapid clock removal; Move from Map in BlockRedstoneTorch to here
public final net.minecraft.world.level.levelgen.BitRandomSource simpleRandom = org.dreeam.leaf.config.modules.opt.FastRNG.enabled ? new org.dreeam.leaf.util.math.random.FasterRandomSource(net.minecraft.world.level.levelgen.RandomSupport.generateUniqueSeed()) : new ca.spottedleaf.moonrise.common.util.SimpleThreadUnsafeRandom(net.minecraft.world.level.levelgen.RandomSupport.generateUniqueSeed()); // Gale - Pufferfish - move random tick random // Leaf - Faster random generator
diff --git a/leaf-server/minecraft-patches/features/0197-Use-BFS-on-getSlopeDistance.patch b/leaf-server/minecraft-patches/features/0197-Use-BFS-on-getSlopeDistance.patch
index 6ea48d0dc8..f1bb96b6e5 100644
--- a/leaf-server/minecraft-patches/features/0197-Use-BFS-on-getSlopeDistance.patch
+++ b/leaf-server/minecraft-patches/features/0197-Use-BFS-on-getSlopeDistance.patch
@@ -9,10 +9,10 @@ Leaf: ~48ms (-36%)
This should help drastically on the farms that use actively changing fluids.
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 97e84257c6d22f21e62bf08e8f2e731a1b806340..a00931986626c6c8e9934fd130ea1c49d5ea71c9 100644
+index d2446c36e559a55e771bc3fedbae73444fef8b14..affe5d48a415929db2ad25c3bdaa7df706cf068d 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1600,6 +1600,10 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1617,6 +1617,10 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
this.emptyTime = 0;
}
diff --git a/leaf-server/minecraft-patches/features/0200-Raytrace-AntiXray-SDK-integration.patch b/leaf-server/minecraft-patches/features/0200-Raytrace-AntiXray-SDK-integration.patch
index 6e79a6020f..c4aecb5612 100644
--- a/leaf-server/minecraft-patches/features/0200-Raytrace-AntiXray-SDK-integration.patch
+++ b/leaf-server/minecraft-patches/features/0200-Raytrace-AntiXray-SDK-integration.patch
@@ -25,10 +25,10 @@ index 185a14c96741d9c394c07b1c7747e908282e9452..df23b1b27028ccb948252f0cd51244ed
}
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 909b22783a482c9d96f088f288305323e8487149..a78e0e8f7c0be8727368efa4bd801fcc2b6c5735 100644
+index 8f934577eed411bdca128e5bd8de86d86be7238c..9e99842ed56be3cad364c84f50eb55fc9c17c3da 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
-@@ -1158,6 +1158,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1168,6 +1168,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
}
// CraftBukkit end - capture blockstates
BlockState oldState = chunk.setBlockState(pos, blockState, updateFlags);
diff --git a/leaf-server/minecraft-patches/features/0227-optimize-mob-spawning.patch b/leaf-server/minecraft-patches/features/0227-optimize-mob-spawning.patch
index cfc04398d2..aaff6e38a2 100644
--- a/leaf-server/minecraft-patches/features/0227-optimize-mob-spawning.patch
+++ b/leaf-server/minecraft-patches/features/0227-optimize-mob-spawning.patch
@@ -191,10 +191,10 @@ index d9d0a7693e86c244af47513ae1a3e39e2bb63a31..b80b408d8b087e37b06e9ad4c096e0b6
}
}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index a00931986626c6c8e9934fd130ea1c49d5ea71c9..93f59186036aaaefde39907d41a2f635edd3cf40 100644
+index affe5d48a415929db2ad25c3bdaa7df706cf068d..a758fab1b83b024370070b30f5ec76620a72677b 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1210,6 +1210,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1227,6 +1227,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
// Paper end - optimise random ticking
private final org.dreeam.leaf.world.DespawnMap despawnMap = new org.dreeam.leaf.world.DespawnMap(); // Leaf - optimize despawn
diff --git a/leaf-server/minecraft-patches/features/0234-optimize-random-tick.patch b/leaf-server/minecraft-patches/features/0234-optimize-random-tick.patch
index 48ddcfd135..42663175ce 100644
--- a/leaf-server/minecraft-patches/features/0234-optimize-random-tick.patch
+++ b/leaf-server/minecraft-patches/features/0234-optimize-random-tick.patch
@@ -24,10 +24,10 @@ index b80b408d8b087e37b06e9ad4c096e0b6cb7c0a86..78ec485515b53a4d61d577b4a688f127
profiler.popPush("customSpawners");
this.level.tickCustomSpawners(this.spawnEnemies);
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 93f59186036aaaefde39907d41a2f635edd3cf40..04c5dbabc43f822d3bc5c640d985801a5c24a4ce 100644
+index a758fab1b83b024370070b30f5ec76620a72677b..4c2372a4404d110bf7d73a0060dedb8381c5e6a2 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1211,6 +1211,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1228,6 +1228,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
private final org.dreeam.leaf.world.DespawnMap despawnMap = new org.dreeam.leaf.world.DespawnMap(); // Leaf - optimize despawn
public final org.dreeam.leaf.world.NatureSpawnChunkMap natureSpawnChunkMap = new org.dreeam.leaf.world.NatureSpawnChunkMap(); // Leaf - optimize mob spawning
diff --git a/leaf-server/minecraft-patches/features/0239-Paw-optimization.patch b/leaf-server/minecraft-patches/features/0239-Paw-optimization.patch
index 9bb7dadd85..3dd042efb0 100644
--- a/leaf-server/minecraft-patches/features/0239-Paw-optimization.patch
+++ b/leaf-server/minecraft-patches/features/0239-Paw-optimization.patch
@@ -81,10 +81,10 @@ index 78ec485515b53a4d61d577b4a688f12722655cf9..b26d7cbe440abf35952c1bab3a2487cf
profiler.popPush("tickSpawningChunks");
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 04c5dbabc43f822d3bc5c640d985801a5c24a4ce..b8a9d7b84567ba290211428b2ebfcef2ce02fdbd 100644
+index 4c2372a4404d110bf7d73a0060dedb8381c5e6a2..18e925fb1bbe1e558041bd3dba690634b551691f 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1639,26 +1639,8 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1656,26 +1656,8 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
}
@@ -111,7 +111,7 @@ index 04c5dbabc43f822d3bc5c640d985801a5c24a4ce..b8a9d7b84567ba290211428b2ebfcef2
entity.setOldPosAndRot();
ProfilerFiller profiler = Profiler.get();
entity.tickCount++;
-@@ -1675,13 +1657,6 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1692,13 +1674,6 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
for (Entity passenger : entity.getPassengers()) {
this.tickPassenger(entity, passenger, isActive); // Paper - EAR 2
}
diff --git a/leaf-server/minecraft-patches/features/0248-Paper-PR-Optimise-temptation-lookups.patch b/leaf-server/minecraft-patches/features/0248-Paper-PR-Optimise-temptation-lookups.patch
index 3245b74f3c..5b4f3003e4 100644
--- a/leaf-server/minecraft-patches/features/0248-Paper-PR-Optimise-temptation-lookups.patch
+++ b/leaf-server/minecraft-patches/features/0248-Paper-PR-Optimise-temptation-lookups.patch
@@ -113,10 +113,10 @@ index 0000000000000000000000000000000000000000..c3339b22929cb4e3b216aadf1069daa1
+ }
+}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index b8a9d7b84567ba290211428b2ebfcef2ce02fdbd..a7693e9cd38a36664033305d34d6806fd74a665c 100644
+index 18e925fb1bbe1e558041bd3dba690634b551691f..65b0637a32f91a59f8c6fc73c7b1c01cd2d3203f 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1067,6 +1067,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1084,6 +1084,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
io.papermc.paper.entity.activation.ActivationRange.activateEntities(this); // Paper - EAR
boolean didDespawn = tickRateManager.runsNormally() && despawnMap.tick(this, this.entityTickList); // Leaf - optimize despawn
@@ -124,7 +124,7 @@ index b8a9d7b84567ba290211428b2ebfcef2ce02fdbd..a7693e9cd38a36664033305d34d6806f
this.entityTickList
.forEach(
entity -> {
-@@ -3294,4 +3295,11 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -3311,4 +3312,11 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
this.lagCompensationTick = (System.nanoTime() - MinecraftServer.SERVER_INIT) / (java.util.concurrent.TimeUnit.MILLISECONDS.toNanos(50L));
}
// Paper end - lag compensation
diff --git a/leaf-server/minecraft-patches/features/0249-Paper-PR-Optimise-temptation-lookups-changes.patch b/leaf-server/minecraft-patches/features/0249-Paper-PR-Optimise-temptation-lookups-changes.patch
index 99c27b6c8b..5d20bfc755 100644
--- a/leaf-server/minecraft-patches/features/0249-Paper-PR-Optimise-temptation-lookups-changes.patch
+++ b/leaf-server/minecraft-patches/features/0249-Paper-PR-Optimise-temptation-lookups-changes.patch
@@ -95,10 +95,10 @@ index c3339b22929cb4e3b216aadf1069daa1f792d74e..3a9fb2524df84fe332eb94c3efe22876
+ // Leaf end - Paper PR: Optimise temptation lookups changes
}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index a7693e9cd38a36664033305d34d6806fd74a665c..6e2226db4c8b09b1e596e1908314886e8c470141 100644
+index 65b0637a32f91a59f8c6fc73c7b1c01cd2d3203f..52270b9fe7d3038a43ee302a1a48ff81934efb4a 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1067,7 +1067,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1084,7 +1084,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
io.papermc.paper.entity.activation.ActivationRange.activateEntities(this); // Paper - EAR
boolean didDespawn = tickRateManager.runsNormally() && despawnMap.tick(this, this.entityTickList); // Leaf - optimize despawn
diff --git a/leaf-server/minecraft-patches/features/0255-thread-unsafe-chunk-map.patch b/leaf-server/minecraft-patches/features/0255-thread-unsafe-chunk-map.patch
index c050ef5da2..8ec7117a66 100644
--- a/leaf-server/minecraft-patches/features/0255-thread-unsafe-chunk-map.patch
+++ b/leaf-server/minecraft-patches/features/0255-thread-unsafe-chunk-map.patch
@@ -190,10 +190,10 @@ index b26d7cbe440abf35952c1bab3a2487cf0be7d6e4..b3de0c91e4e53b8b554128ec86656597
return ret;
}
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index a78e0e8f7c0be8727368efa4bd801fcc2b6c5735..7b5b35c206798a0ac904f7a7389a0468018dd11c 100644
+index 9e99842ed56be3cad364c84f50eb55fc9c17c3da..00eddc54d7b1fc83b648df1fb2f5eb05b7e22521 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
-@@ -1042,6 +1042,16 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1052,6 +1052,16 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
// Paper end - Perf: make sure loaded chunks get the inlined variant of this function
}
diff --git a/leaf-server/minecraft-patches/features/0257-optimize-get-chunk.patch b/leaf-server/minecraft-patches/features/0257-optimize-get-chunk.patch
index 10910694c8..d3be5a6b65 100644
--- a/leaf-server/minecraft-patches/features/0257-optimize-get-chunk.patch
+++ b/leaf-server/minecraft-patches/features/0257-optimize-get-chunk.patch
@@ -5,10 +5,10 @@ Subject: [PATCH] optimize get chunk
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 7b5b35c206798a0ac904f7a7389a0468018dd11c..a1d43377cbc6a37fe3199ecf886b511c879b9904 100644
+index 00eddc54d7b1fc83b648df1fb2f5eb05b7e22521..6dc9c01356a991cebf2e1102d2c2f8cb5f0fa23e 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
-@@ -1395,12 +1395,17 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1405,12 +1405,17 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
}
}
// CraftBukkit end
diff --git a/leaf-server/minecraft-patches/features/0258-remove-shouldTickBlocksAt-check.patch b/leaf-server/minecraft-patches/features/0258-remove-shouldTickBlocksAt-check.patch
index 79507f1028..19f35d30ea 100644
--- a/leaf-server/minecraft-patches/features/0258-remove-shouldTickBlocksAt-check.patch
+++ b/leaf-server/minecraft-patches/features/0258-remove-shouldTickBlocksAt-check.patch
@@ -5,10 +5,10 @@ Subject: [PATCH] remove shouldTickBlocksAt check
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index a1d43377cbc6a37fe3199ecf886b511c879b9904..c748af5c1b443bcb4863dd14cc206f36bfef640b 100644
+index 6dc9c01356a991cebf2e1102d2c2f8cb5f0fa23e..69e9861df42d2f03c2301cad1c28225e17df9926 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
-@@ -1585,7 +1585,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1595,7 +1595,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
// Paper end - Fix MC-117075 use removeAll
if (ticker.isRemoved()) {
((org.dreeam.leaf.util.list.BlockEntityTickersList) this.blockEntityTickers).markAsRemoved(tickerIndex); // Paper - Fix MC-117075; use removeAll // SparklyPaper - optimize block entity removals
diff --git a/leaf-server/minecraft-patches/features/0262-optimize-fluid-state-access.patch b/leaf-server/minecraft-patches/features/0262-optimize-fluid-state-access.patch
index f3dff87baf..84500e19b8 100644
--- a/leaf-server/minecraft-patches/features/0262-optimize-fluid-state-access.patch
+++ b/leaf-server/minecraft-patches/features/0262-optimize-fluid-state-access.patch
@@ -5,10 +5,10 @@ Subject: [PATCH] optimize fluid state access
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index c748af5c1b443bcb4863dd14cc206f36bfef640b..8658c54d489a0f9561b19dd371346c0acb7c9fd5 100644
+index 69e9861df42d2f03c2301cad1c28225e17df9926..6f730baf229b768d8845da3b526890adebe6d682 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
-@@ -1418,6 +1418,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1428,6 +1428,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
return chunk.getFluidState(pos);
}
diff --git a/leaf-server/minecraft-patches/features/0267-cache-collision-list.patch b/leaf-server/minecraft-patches/features/0267-cache-collision-list.patch
index 802a5edccb..583d854cad 100644
--- a/leaf-server/minecraft-patches/features/0267-cache-collision-list.patch
+++ b/leaf-server/minecraft-patches/features/0267-cache-collision-list.patch
@@ -5,10 +5,10 @@ Subject: [PATCH] cache collision list
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 6e2226db4c8b09b1e596e1908314886e8c470141..5ecf7a51bd8056cde945edcc53e66ef0b0aca25e 100644
+index 52270b9fe7d3038a43ee302a1a48ff81934efb4a..34218952dba0869126c75990495a4bc85ef3b76a 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1213,6 +1213,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1230,6 +1230,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
private final org.dreeam.leaf.world.DespawnMap despawnMap = new org.dreeam.leaf.world.DespawnMap(); // Leaf - optimize despawn
public final org.dreeam.leaf.world.NatureSpawnChunkMap natureSpawnChunkMap = new org.dreeam.leaf.world.NatureSpawnChunkMap(); // Leaf - optimize mob spawning
public final org.dreeam.leaf.world.RandomTickSystem randomTickSystem = new org.dreeam.leaf.world.RandomTickSystem(); // Leaf - optimize random tick
diff --git a/leaf-server/minecraft-patches/features/0268-fast-bit-radix-sort.patch b/leaf-server/minecraft-patches/features/0268-fast-bit-radix-sort.patch
index 87390ed33b..593fd7f3c5 100644
--- a/leaf-server/minecraft-patches/features/0268-fast-bit-radix-sort.patch
+++ b/leaf-server/minecraft-patches/features/0268-fast-bit-radix-sort.patch
@@ -6,10 +6,10 @@ Subject: [PATCH] fast bit radix sort
Co-authored-by: Taiyou06
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 5ecf7a51bd8056cde945edcc53e66ef0b0aca25e..55bfbd57bdfe8e7b757ed097bbda1ab5b1a8a4df 100644
+index 34218952dba0869126c75990495a4bc85ef3b76a..c1baa95d4ea660c4ebc16c662532618cf64aa58f 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1214,6 +1214,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1231,6 +1231,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
public final org.dreeam.leaf.world.NatureSpawnChunkMap natureSpawnChunkMap = new org.dreeam.leaf.world.NatureSpawnChunkMap(); // Leaf - optimize mob spawning
public final org.dreeam.leaf.world.RandomTickSystem randomTickSystem = new org.dreeam.leaf.world.RandomTickSystem(); // Leaf - optimize random tick
public final org.dreeam.leaf.world.EntityCollisionCache entityCollisionCache = new org.dreeam.leaf.world.EntityCollisionCache(); // Leaf - cache collision list
diff --git a/leaf-server/minecraft-patches/features/0270-Pluto-Expose-Direction-Plane-s-faces.patch b/leaf-server/minecraft-patches/features/0270-Pluto-Expose-Direction-Plane-s-faces.patch
index 317b9ecc1e..13d4cc2a21 100644
--- a/leaf-server/minecraft-patches/features/0270-Pluto-Expose-Direction-Plane-s-faces.patch
+++ b/leaf-server/minecraft-patches/features/0270-Pluto-Expose-Direction-Plane-s-faces.patch
@@ -35,10 +35,10 @@ index 9548fea3c629db7829e4b83a18a7749d1f15f6a4..09e727f03fa6731b3325478c43aed3cb
}
}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 55bfbd57bdfe8e7b757ed097bbda1ab5b1a8a4df..39168ac149fe9c7f076e07c6580320eb771e09e6 100644
+index c1baa95d4ea660c4ebc16c662532618cf64aa58f..976096e70031e5a923d23d129f14fb137d548957 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1306,7 +1306,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1323,7 +1323,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
// We only need to check blocks that are taller than the minimum step height
if (org.purpurmc.purpur.PurpurConfig.smoothSnowAccumulationStep > 0 && currentLayers >= org.purpurmc.purpur.PurpurConfig.smoothSnowAccumulationStep) {
int layersValueMin = currentLayers - org.purpurmc.purpur.PurpurConfig.smoothSnowAccumulationStep;
@@ -100,10 +100,10 @@ index 187cca14c69b2b472770d949cf61e4c0b7e098c6..668316a966bed87f2ef4aa8a9c5bf194
}
}
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 8658c54d489a0f9561b19dd371346c0acb7c9fd5..90009387d495db3fb3f68cd7d1ebe469b9aa1c9a 100644
+index 6f730baf229b768d8845da3b526890adebe6d682..7224f2a8968745bb7231cf78a1d6f2da53656a01 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
-@@ -2150,7 +2150,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -2160,7 +2160,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
public abstract Scoreboard getScoreboard();
public void updateNeighbourForOutputSignal(final BlockPos pos, final Block changedBlock) {
diff --git a/leaf-server/minecraft-patches/features/0275-Multithreaded-Tracker.patch b/leaf-server/minecraft-patches/features/0275-Multithreaded-Tracker.patch
index 52eec2b16c..f2d2fbd538 100644
--- a/leaf-server/minecraft-patches/features/0275-Multithreaded-Tracker.patch
+++ b/leaf-server/minecraft-patches/features/0275-Multithreaded-Tracker.patch
@@ -1059,7 +1059,7 @@ index 3ae5196e0a0fa5b8c8f590b3cf6345993c459e7c..5b7fe0ecf878b500cfd35ddf1349ec55
void sendToTrackingPlayers(Packet super ClientGamePacketListener> packet);
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 39168ac149fe9c7f076e07c6580320eb771e09e6..b4bb4b8e7cf12473e775a5d794346c2fd18db957 100644
+index 976096e70031e5a923d23d129f14fb137d548957..8e0cf3c0d8848b9444a708adc0c150354c881e99 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -244,6 +244,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -1070,7 +1070,7 @@ index 39168ac149fe9c7f076e07c6580320eb771e09e6..b4bb4b8e7cf12473e775a5d794346c2f
@Override
public @Nullable LevelChunk getChunkIfLoaded(int x, int z) {
-@@ -1129,6 +1130,8 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1146,6 +1147,8 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
this.debugSynchronizers.tick(this.server.debugSubscribers());
profiler.pop();
diff --git a/leaf-server/minecraft-patches/features/0277-Rewrite-entity-despawn-time.patch b/leaf-server/minecraft-patches/features/0277-Rewrite-entity-despawn-time.patch
index ef1d160cc8..ee1ab210c2 100644
--- a/leaf-server/minecraft-patches/features/0277-Rewrite-entity-despawn-time.patch
+++ b/leaf-server/minecraft-patches/features/0277-Rewrite-entity-despawn-time.patch
@@ -97,10 +97,10 @@ index 6d6b1a261708b88a4b706ccc51b7d397fe42ea6b..f27f198e84ac668446c9d57772343ba1
private final ChunkEntitySlices[] slices = new ChunkEntitySlices[REGION_SIZE * REGION_SIZE];
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index b4bb4b8e7cf12473e775a5d794346c2fd18db957..7d095d078f01460c532dfe72e760d702528efc69 100644
+index 8e0cf3c0d8848b9444a708adc0c150354c881e99..8358864ab518c04af208bfb64e6362780c3a1b04 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1131,6 +1131,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1148,6 +1148,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
this.debugSynchronizers.tick(this.server.debugSubscribers());
profiler.pop();
this.environmentAttributes().invalidateTickCache();
@@ -108,7 +108,7 @@ index b4bb4b8e7cf12473e775a5d794346c2fd18db957..7d095d078f01460c532dfe72e760d702
if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { this.leaf$asyncTracker.onEntitiesTickEnd(); } // Leaf - Multithreaded tracker
}
-@@ -1660,6 +1661,8 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1677,6 +1678,8 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
} else {entity.inactiveTick();} // Paper - EAR 2
profiler.pop();
@@ -117,7 +117,7 @@ index b4bb4b8e7cf12473e775a5d794346c2fd18db957..7d095d078f01460c532dfe72e760d702
for (Entity passenger : entity.getPassengers()) {
this.tickPassenger(entity, passenger, isActive); // Paper - EAR 2
}
-@@ -1688,6 +1691,8 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1705,6 +1708,8 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
// Paper end - EAR 2
profiler.pop();
diff --git a/leaf-server/minecraft-patches/features/0279-Cache-world-border.patch b/leaf-server/minecraft-patches/features/0279-Cache-world-border.patch
index e10e43046a..248bfad0e6 100644
--- a/leaf-server/minecraft-patches/features/0279-Cache-world-border.patch
+++ b/leaf-server/minecraft-patches/features/0279-Cache-world-border.patch
@@ -7,7 +7,7 @@ The world border is only initialized once when the level is created.
Lookup from data storage map multiple time is quite unnecessary and expensive.
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 7d095d078f01460c532dfe72e760d702528efc69..78fcc323e8a5c1f7be5769412d7f58f9a36f464f 100644
+index 8358864ab518c04af208bfb64e6362780c3a1b04..6c26eb69cbe7ae0e732e1c89a2fddcc94a3e5707 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -245,6 +245,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -18,7 +18,7 @@ index 7d095d078f01460c532dfe72e760d702528efc69..78fcc323e8a5c1f7be5769412d7f58f9
@Override
public @Nullable LevelChunk getChunkIfLoaded(int x, int z) {
-@@ -2582,8 +2583,14 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -2599,8 +2600,14 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@Override
public WorldBorder getWorldBorder() {
diff --git a/leaf-server/minecraft-patches/features/0283-Reduce-optimiseRandomTick-new-BlockPos-instance-crea.patch b/leaf-server/minecraft-patches/features/0283-Reduce-optimiseRandomTick-new-BlockPos-instance-crea.patch
index 4a24d360ef..2db36d593a 100644
--- a/leaf-server/minecraft-patches/features/0283-Reduce-optimiseRandomTick-new-BlockPos-instance-crea.patch
+++ b/leaf-server/minecraft-patches/features/0283-Reduce-optimiseRandomTick-new-BlockPos-instance-crea.patch
@@ -27,10 +27,10 @@ index ccc8a5b5a5353e624e86d69617599cc3ac118a9d..480031124848027a006f62a6bbf90c94
// Perform the walk over all directly reachable redstone wire blocks, propagating wire value
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 78fcc323e8a5c1f7be5769412d7f58f9a36f464f..7f84054524547a133f81acbdd85d4c3d2f157fd2 100644
+index 6c26eb69cbe7ae0e732e1c89a2fddcc94a3e5707..7e299dd642d2b2ea5342331f9a1237340c5ceb57 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1165,6 +1165,8 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1182,6 +1182,8 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
this.players.stream().filter(LivingEntity::isSleeping).collect(Collectors.toList()).forEach(player -> player.stopSleepInBed(false, false));
}
@@ -39,7 +39,7 @@ index 78fcc323e8a5c1f7be5769412d7f58f9a36f464f..7f84054524547a133f81acbdd85d4c3d
// Paper start - optimise random ticking
private void optimiseRandomTick(final LevelChunk chunk, final int tickSpeed) {
final LevelChunkSection[] sections = chunk.getSections();
-@@ -1198,14 +1200,16 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1215,14 +1217,16 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
final int location = (int)tickList.getRaw(index) & 0xFFFF;
final BlockState state = states.get(location);
diff --git a/leaf-server/minecraft-patches/features/0299-configurable-ice-and-snow-tick-chance.patch b/leaf-server/minecraft-patches/features/0299-configurable-ice-and-snow-tick-chance.patch
index aa62e07326..b72169a634 100644
--- a/leaf-server/minecraft-patches/features/0299-configurable-ice-and-snow-tick-chance.patch
+++ b/leaf-server/minecraft-patches/features/0299-configurable-ice-and-snow-tick-chance.patch
@@ -5,10 +5,10 @@ Subject: [PATCH] configurable ice and snow tick chance
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index f19fd8392e914d3cc7f92c173ca2192e18ec6762..8f179b23d038c6dec27fffacec8736151d7399b6 100644
+index 64c39eb7d15c08a0b61ce4c469c1ee65ff1456f0..f7f82e4904771cedc17ffc5f5d5596b4e528668a 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1264,9 +1264,10 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1281,9 +1281,10 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
ProfilerFiller profiler = Profiler.get();
profiler.push("iceandsnow");
diff --git a/leaf-server/minecraft-patches/features/0302-disable-world-data-saving.patch b/leaf-server/minecraft-patches/features/0302-disable-world-data-saving.patch
index 03b3afd493..6ec1d70fe0 100644
--- a/leaf-server/minecraft-patches/features/0302-disable-world-data-saving.patch
+++ b/leaf-server/minecraft-patches/features/0302-disable-world-data-saving.patch
@@ -50,10 +50,10 @@ index 9a9a599ef178f851ee5c783631a724013a693586..f950472f371af3581af82c5bcdd7a800
final CompoundTag save = poi.save();
poi.setDirty(false);
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 8f179b23d038c6dec27fffacec8736151d7399b6..44b453c443fd5bfc04c9e2ecbc486db263cf89a1 100644
+index f7f82e4904771cedc17ffc5f5d5596b4e528668a..886ab1eb3c30da6b460a2ec2f1d1da0679061208 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1757,6 +1757,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1774,6 +1774,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
}
// Paper start - Incremental chunk and player saving
public void saveIncrementally(final boolean doFull) {
@@ -61,7 +61,7 @@ index 8f179b23d038c6dec27fffacec8736151d7399b6..44b453c443fd5bfc04c9e2ecbc486db2
if (doFull) {
org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(this.getWorld()));
this.saveLevelData(false);
-@@ -1772,6 +1773,18 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1789,6 +1790,18 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
public void save(final @Nullable ProgressListener progressListener, final boolean flush, final boolean noSave, final boolean close) {
// Paper end - add close param
ServerChunkCache chunkSource = this.getChunkSource();
diff --git a/leaf-server/minecraft-patches/features/0304-Leaves-Lithium-Sleeping-Block-Entity.patch b/leaf-server/minecraft-patches/features/0304-Leaves-Lithium-Sleeping-Block-Entity.patch
index 12bb0a6f03..bf143b72c4 100644
--- a/leaf-server/minecraft-patches/features/0304-Leaves-Lithium-Sleeping-Block-Entity.patch
+++ b/leaf-server/minecraft-patches/features/0304-Leaves-Lithium-Sleeping-Block-Entity.patch
@@ -54,10 +54,10 @@ index f2c5a6ce769d8a5fdb4b836eae6a61a4da91cb3f..c6ce0318e27ca49d9bb68f425ab92089
}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 44b453c443fd5bfc04c9e2ecbc486db263cf89a1..4c8df94108449cd37a38e497b44af6dfcc18af96 100644
+index 886ab1eb3c30da6b460a2ec2f1d1da0679061208..abf8a242f48b666d371bffeb26f63e12c5e9d6b1 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -2888,6 +2888,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -2905,6 +2905,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
for (TickingBlockEntity ticker : this.blockEntityTickers) {
BlockPos blockPos = ticker.getPos();
@@ -281,10 +281,10 @@ index 27122aa287626a7deb70bad5b601fe086e268b82..006a3c8bf13d93570e264added86a179
}
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 90009387d495db3fb3f68cd7d1ebe469b9aa1c9a..6e456da74355646a649473637386a391e98f1cc5 100644
+index 7224f2a8968745bb7231cf78a1d6f2da53656a01..dc4ec06a7f407a438fd816af66915bb58266a7f0 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
-@@ -2308,4 +2308,25 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -2318,4 +2318,25 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
return getWorld().getEnvironment() == org.bukkit.World.Environment.THE_END;
}
// Purpur end - Add allow water in end world option
diff --git a/leaf-server/minecraft-patches/features/0305-fixup-Leaves-Lithium-Sleeping-Block-Entity.patch b/leaf-server/minecraft-patches/features/0305-fixup-Leaves-Lithium-Sleeping-Block-Entity.patch
index a9041f1e88..d2e9aeac37 100644
--- a/leaf-server/minecraft-patches/features/0305-fixup-Leaves-Lithium-Sleeping-Block-Entity.patch
+++ b/leaf-server/minecraft-patches/features/0305-fixup-Leaves-Lithium-Sleeping-Block-Entity.patch
@@ -25,10 +25,10 @@ https://github.com/CaffeineMC/lithium/commit/ba08831d0254076cba1e8aba3db6c42f26d
https://github.com/CaffeineMC/lithium/commit/ba089f52be5b26e6590402211581e05e467a8e2d
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 4c8df94108449cd37a38e497b44af6dfcc18af96..3c6bde03919d8580b444c5b1851b15936899bbe5 100644
+index abf8a242f48b666d371bffeb26f63e12c5e9d6b1..c225c348a5302199c035a7b1373a1fe76442f936 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -2888,7 +2888,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -2905,7 +2905,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
for (TickingBlockEntity ticker : this.blockEntityTickers) {
BlockPos blockPos = ticker.getPos();
diff --git a/leaf-server/minecraft-patches/features/0309-Add-read-only-mode-for-Linear-v2.patch b/leaf-server/minecraft-patches/features/0309-Add-read-only-mode-for-Linear-v2.patch
index 5383ebecd0..6109f5e627 100644
--- a/leaf-server/minecraft-patches/features/0309-Add-read-only-mode-for-Linear-v2.patch
+++ b/leaf-server/minecraft-patches/features/0309-Add-read-only-mode-for-Linear-v2.patch
@@ -47,10 +47,10 @@ index 5a8937740f7779f971c2a13ec580eed7bcc26395..98b48a8a6f6372114ee1f71b2c69bff9
try {
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 3c6bde03919d8580b444c5b1851b15936899bbe5..908adae851185b48b8dc4ba2e7efad02402fba04 100644
+index c225c348a5302199c035a7b1373a1fe76442f936..23dad94f3239b4b7a582514e4e254e18a47f36a7 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1758,6 +1758,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1775,6 +1775,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
// Paper start - Incremental chunk and player saving
public void saveIncrementally(final boolean doFull) {
if (org.dreeam.leaf.config.modules.misc.DisableWorldDataSaving.shouldSkipSave(this)) return; // Leaf - disable world data saving
@@ -58,7 +58,7 @@ index 3c6bde03919d8580b444c5b1851b15936899bbe5..908adae851185b48b8dc4ba2e7efad02
if (doFull) {
org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(this.getWorld()));
this.saveLevelData(false);
-@@ -1774,7 +1775,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1791,7 +1792,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
// Paper end - add close param
ServerChunkCache chunkSource = this.getChunkSource();
// Leaf start - disable world data saving
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigBinder.java b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigBinder.java
new file mode 100644
index 0000000000..b8264a0ce3
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigBinder.java
@@ -0,0 +1,171 @@
+package org.dreeam.leaf.config;
+
+import org.dreeam.leaf.config.annotations.ConfigClassInfo;
+import org.dreeam.leaf.config.annotations.ConfigInfo;
+import org.dreeam.leaf.config.annotations.DoNotLoad;
+import org.dreeam.leaf.config.annotations.HotReloadUnsupported;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Binds annotation-driven module fields to a global or world configuration view.
+ */
+final class ConfigBinder {
+
+ private ConfigBinder() {
+ }
+
+ static void bindGlobal(
+ ConfigModule module,
+ LeafConfigAccessor config,
+ boolean alreadyInitialized
+ ) throws IllegalAccessException {
+ bind(module, config, true, alreadyInitialized);
+ }
+
+ static void bindWorld(
+ WorldConfigModule module,
+ LeafWorldConfig config,
+ boolean alreadyInitialized
+ ) throws IllegalAccessException {
+ bind(module, config, false, alreadyInitialized);
+ }
+
+ private static void bind(
+ ConfigModule module,
+ LeafConfigAccessor config,
+ boolean global,
+ boolean alreadyInitialized
+ ) throws IllegalAccessException {
+ Class> moduleClass = module.getClass();
+ ConfigClassInfo classInfo = moduleClass.getAnnotation(ConfigClassInfo.class);
+ if (classInfo == null) {
+ throw new IllegalStateException("Configuration module " + moduleClass.getName()
+ + " is missing @ConfigClassInfo");
+ }
+
+ String basePath = basePath(classInfo);
+ if (!classInfo.comments().isBlank()
+ && (!(config instanceof LeafWorldConfig worldConfig) || worldConfig.isDefaultsConfig())) {
+ config.addComment(basePath, classInfo.comments());
+ }
+
+ for (Field field : moduleClass.getDeclaredFields()) {
+ boolean skipLoad = field.getAnnotation(DoNotLoad.class) != null;
+ boolean doNotReload = alreadyInitialized
+ && field.getAnnotation(HotReloadUnsupported.class) != null;
+ ConfigInfo configInfo = field.getAnnotation(ConfigInfo.class);
+ if (skipLoad || configInfo == null) {
+ continue;
+ }
+
+ validateField(moduleClass, field, global);
+ field.setAccessible(true);
+
+ Object target = global ? null : module;
+ Object defaultValue = field.get(target);
+ Object loadedValue = readValue(config, path(basePath, configInfo), configInfo.comments(),
+ field, defaultValue);
+ if (!doNotReload) {
+ field.set(target, loadedValue);
+ }
+ }
+ }
+
+ private static void validateField(Class> moduleClass, Field field, boolean global) {
+ int modifiers = field.getModifiers();
+ if (Modifier.isFinal(modifiers)) {
+ throw new IllegalStateException("@ConfigInfo field must be mutable: "
+ + moduleClass.getName() + "." + field.getName());
+ }
+ if (Modifier.isStatic(modifiers) != global) {
+ String expected = global ? "static" : "an instance field";
+ throw new IllegalStateException("@ConfigInfo field must be " + expected + ": "
+ + moduleClass.getName() + "." + field.getName());
+ }
+ }
+
+ private static String basePath(ConfigClassInfo info) {
+ List path = new ArrayList<>();
+ path.add(info.category().basePath());
+ path.addAll(List.of(info.directory()));
+ path.add(info.name());
+ return joinPath(path);
+ }
+
+ private static String path(String basePath, ConfigInfo info) {
+ List path = new ArrayList<>();
+ path.add(basePath);
+ path.addAll(List.of(info.directory()));
+ path.add(info.name());
+ return joinPath(path);
+ }
+
+ private static String joinPath(List path) {
+ if (path.stream().anyMatch(String::isBlank)) {
+ throw new IllegalStateException("Configuration path segments must not be blank: " + path);
+ }
+ return String.join(".", path);
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ private static Object readValue(
+ LeafConfigAccessor config,
+ String path,
+ String comment,
+ Field field,
+ Object defaultValue
+ ) {
+ if (defaultValue == null) {
+ throw new IllegalStateException("Configuration field has a null default value: " + field);
+ }
+
+ Class> type = field.getType();
+ if (type == boolean.class || type == Boolean.class) {
+ return comment.isBlank()
+ ? config.getBoolean(path, (Boolean) defaultValue)
+ : config.getBoolean(path, (Boolean) defaultValue, comment);
+ }
+ if (type == int.class || type == Integer.class) {
+ return comment.isBlank()
+ ? config.getInt(path, (Integer) defaultValue)
+ : config.getInt(path, (Integer) defaultValue, comment);
+ }
+ if (type == long.class || type == Long.class) {
+ return comment.isBlank()
+ ? config.getLong(path, (Long) defaultValue)
+ : config.getLong(path, (Long) defaultValue, comment);
+ }
+ if (type == double.class || type == Double.class) {
+ return comment.isBlank()
+ ? config.getDouble(path, (Double) defaultValue)
+ : config.getDouble(path, (Double) defaultValue, comment);
+ }
+ if (type == String.class) {
+ return comment.isBlank()
+ ? config.getString(path, (String) defaultValue)
+ : config.getString(path, (String) defaultValue, comment);
+ }
+ if (List.class.isAssignableFrom(type)) {
+ return comment.isBlank()
+ ? config.getList(path, (List) defaultValue)
+ : config.getList(path, (List) defaultValue, comment);
+ }
+ if (type.isEnum()) {
+ String value = comment.isBlank()
+ ? config.getString(path, ((Enum>) defaultValue).name())
+ : config.getString(path, ((Enum>) defaultValue).name(), comment);
+ try {
+ return Enum.valueOf((Class extends Enum>) type, value.toUpperCase(Locale.ROOT));
+ } catch (IllegalArgumentException exception) {
+ throw new IllegalArgumentException("Invalid value '" + value + "' for " + path
+ + "; expected one of " + List.of(type.getEnumConstants()), exception);
+ }
+ }
+ throw new IllegalArgumentException("Unsupported @ConfigInfo field type: " + field);
+ }
+}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigCategory.java b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigCategory.java
index 8465456737..ec43d9d43f 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigCategory.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigCategory.java
@@ -1,5 +1,8 @@
package org.dreeam.leaf.config;
+/**
+ * Categories available to annotation-driven configuration modules.
+ */
public enum ConfigCategory {
ASYNC("async"),
PERF("performance"),
@@ -10,8 +13,6 @@ public enum ConfigCategory {
private final String basePath;
- private static final ConfigCategory[] VALUES = ConfigCategory.values();
-
ConfigCategory(String basePath) {
this.basePath = basePath;
}
@@ -19,8 +20,4 @@ public enum ConfigCategory {
public String basePath() {
return this.basePath;
}
-
- public static ConfigCategory[] categoryValues() {
- return VALUES;
- }
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModule.java b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModule.java
index 5f8647a8c0..08592932d4 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModule.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModule.java
@@ -1,116 +1,35 @@
package org.dreeam.leaf.config;
-import it.unimi.dsi.fastutil.objects.ObjectArrays;
-import org.dreeam.leaf.config.annotations.Experimental;
-
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Modifier;
-import java.util.*;
-
-public abstract class ConfigModule extends LeafConfig {
-
- private static final Set LOADED_MODULES = new HashSet<>();
- private static List> WORLD_MODULES = List.of();
- protected final LeafGlobalConfig globalConfig;
+/**
+ * Marker and lifecycle contract for a server-wide Leaf configuration module.
+ *
+ * Annotated global module fields must be static and mutable. World-scoped modules must
+ * implement {@link WorldConfigModule} instead and use instance fields.
+ */
+public interface ConfigModule {
- public ConfigModule() {
- this.globalConfig = LeafConfig.globalConfig();
+ default void onLoaded() {
}
- public static void initModules() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
- List enabledExperimentalModules = new ArrayList<>();
- List deprecatedModules = new ArrayList<>();
- List> worldModules = new ArrayList<>();
-
- Class>[] classes = LeafConfig.getClasses(LeafConfig.CONFIG_MODULE_PACKAGE).toArray(new Class[0]);
- ObjectArrays.quickSort(classes, Comparator.comparing(Class::getSimpleName));
- for (Class> clazz : classes) {
- ConfigModule module = (ConfigModule) clazz.getConstructor().newInstance();
- module.onLoaded();
-
- if (WorldConfigModule.class.isAssignableFrom(clazz)) {
- @SuppressWarnings("unchecked")
- Class extends WorldConfigModule> worldModuleClass = (Class extends WorldConfigModule>) clazz;
- worldModules.add(worldModuleClass);
- }
-
- LOADED_MODULES.add(module);
- for (Field field : getAnnotatedStaticFields(clazz, Experimental.class)) {
- if (!(field.get(null) instanceof Boolean enabled)) continue;
- if (enabled) {
- enabledExperimentalModules.add(field);
- }
- }
- for (Field field : getAnnotatedStaticFields(clazz, Deprecated.class)) {
- if (!(field.get(null) instanceof Boolean enabled)) continue;
- if (enabled) {
- deprecatedModules.add(field);
- }
- }
- }
-
- if (!enabledExperimentalModules.isEmpty()) {
- LeafConfig.LOGGER.warn("You have following experimental module(s) enabled: {}, please proceed with caution!", formatModules(enabledExperimentalModules));
- }
-
- if (!deprecatedModules.isEmpty()) {
- LeafConfig.LOGGER.warn("The following enabled module(s) has been deprecated: {}, please proceed with caution!", formatModules(deprecatedModules));
- }
-
- WORLD_MODULES = List.copyOf(worldModules);
+ default void onPostLoaded() {
}
- private static List formatModules(List modules) {
- return modules.stream().map(f -> f.getDeclaringClass().getSimpleName() + "." + f.getName()).toList();
+ static void initModules()
+ throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
+ ConfigModuleLoader.initModules();
}
- public static void loadAfterBootstrap() {
- for (ConfigModule module : LOADED_MODULES) {
- module.onPostLoaded();
- }
-
- // Save config to disk
- try {
- LeafConfig.globalConfig().saveConfig();
- } catch (Exception e) {
- LeafConfig.LOGGER.error("Failed to save config file!", e);
- }
- }
-
- private static List getAnnotatedStaticFields(Class> clazz, Class extends Annotation> annotation) {
- List fields = new ArrayList<>();
-
- for (Field field : clazz.getDeclaredFields()) {
- if (field.isAnnotationPresent(annotation) && Modifier.isStatic(field.getModifiers())) {
- field.setAccessible(true);
- fields.add(field);
- }
- }
-
- return fields;
- }
-
- public static void clearModules() {
- LOADED_MODULES.clear();
- WORLD_MODULES = List.of();
+ static void loadAfterBootstrap() {
+ ConfigModuleLoader.loadAfterBootstrap();
}
- /** Instantiates the cached, stateless world modules for one world configuration. */
- public static void loadWorldModules(LeafWorldConfig config) {
- try {
- for (Class extends WorldConfigModule> moduleClass : WORLD_MODULES) {
- moduleClass.getConstructor().newInstance().loadWorldConfig(config);
- }
- } catch (ReflectiveOperationException exception) {
- throw new RuntimeException("Could not load Leaf world configuration modules", exception);
- }
+ static void clearModules() {
+ ConfigModuleLoader.clearModules();
}
- public abstract void onLoaded();
-
- public void onPostLoaded() {
+ static void loadWorldModules(LeafWorldConfig config) {
+ ConfigModuleLoader.loadWorldModules(config);
}
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModuleLoader.java b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModuleLoader.java
new file mode 100644
index 0000000000..ba5849ff0d
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModuleLoader.java
@@ -0,0 +1,142 @@
+package org.dreeam.leaf.config;
+
+import it.unimi.dsi.fastutil.objects.ObjectArrays;
+import org.dreeam.leaf.config.annotations.ConfigClassInfo;
+import org.dreeam.leaf.config.annotations.Experimental;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+final class ConfigModuleLoader {
+
+ private static final Set LOADED_MODULES = new LinkedHashSet<>();
+ private static List> worldModules = List.of();
+ private static boolean alreadyInitialized;
+
+ private ConfigModuleLoader() {
+ }
+
+ static void initModules()
+ throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
+ List enabledExperimentalModules = new ArrayList<>();
+ List deprecatedModules = new ArrayList<>();
+ List> discoveredWorldModules = new ArrayList<>();
+
+ Class>[] classes = LeafConfig.getClasses(LeafConfig.CONFIG_MODULE_PACKAGE).toArray(new Class[0]);
+ ObjectArrays.quickSort(classes, Comparator.comparing((Class> clazz) -> clazz.getSimpleName())
+ .thenComparing(Class::getName));
+ for (Class> moduleClass : classes) {
+ if (!ConfigModule.class.isAssignableFrom(moduleClass)
+ || moduleClass.isInterface()
+ || Modifier.isAbstract(moduleClass.getModifiers())) {
+ continue;
+ }
+
+ if (WorldConfigModule.class.isAssignableFrom(moduleClass)) {
+ @SuppressWarnings("unchecked")
+ Class extends WorldConfigModule> worldModuleClass =
+ (Class extends WorldConfigModule>) moduleClass;
+ discoveredWorldModules.add(worldModuleClass);
+ validateAnnotatedModule(moduleClass);
+ continue;
+ }
+
+ ConfigModule module = (ConfigModule) moduleClass.getConstructor().newInstance();
+ validateAnnotatedModule(moduleClass);
+ ConfigBinder.bindGlobal(module, LeafConfig.globalConfig(), alreadyInitialized);
+ module.onLoaded();
+ LOADED_MODULES.add(module);
+ collectEnabledFields(moduleClass, Experimental.class, enabledExperimentalModules);
+ collectEnabledFields(moduleClass, Deprecated.class, deprecatedModules);
+ }
+
+ if (!enabledExperimentalModules.isEmpty()) {
+ LeafConfig.LOGGER.warn(
+ "You have following experimental module(s) enabled: {}, please proceed with caution!",
+ formatFields(enabledExperimentalModules)
+ );
+ }
+ if (!deprecatedModules.isEmpty()) {
+ LeafConfig.LOGGER.warn(
+ "The following enabled module(s) has been deprecated: {}, please proceed with caution!",
+ formatFields(deprecatedModules)
+ );
+ }
+ worldModules = List.copyOf(discoveredWorldModules);
+ }
+
+ static void loadAfterBootstrap() {
+ for (ConfigModule module : LOADED_MODULES) {
+ module.onPostLoaded();
+ }
+
+ try {
+ LeafConfig.globalConfig().saveConfig();
+ } catch (Exception exception) {
+ LeafConfig.LOGGER.error("Failed to save config file!", exception);
+ }
+ }
+
+ static void loadWorldModules(LeafWorldConfig config) {
+ try {
+ boolean alreadyInitialized = config.isReload();
+ for (Class extends WorldConfigModule> moduleClass : worldModules) {
+ WorldConfigModule module = alreadyInitialized ? config.reloadModule(moduleClass) : null;
+ if (module == null) {
+ module = moduleClass.getConstructor().newInstance();
+ }
+
+ ConfigBinder.bindWorld(module, config, alreadyInitialized);
+ config.registerModule(moduleClass, module);
+ module.onLoaded();
+ }
+ } catch (ReflectiveOperationException exception) {
+ throw new RuntimeException("Could not load Leaf world configuration modules", exception);
+ }
+ }
+
+ static void clearModules() {
+ LOADED_MODULES.clear();
+ worldModules = List.of();
+ }
+
+ static void markInitialized() {
+ alreadyInitialized = true;
+ }
+
+ private static void validateAnnotatedModule(Class> moduleClass) {
+ if (!moduleClass.isAnnotationPresent(ConfigClassInfo.class)) {
+ throw new IllegalStateException("Configuration module " + moduleClass.getName()
+ + " is missing @ConfigClassInfo");
+ }
+ }
+
+ private static void collectEnabledFields(
+ Class> moduleClass,
+ Class extends Annotation> annotation,
+ List enabledFields
+ ) throws IllegalAccessException {
+ for (Field field : moduleClass.getDeclaredFields()) {
+ if (!field.isAnnotationPresent(annotation) || !Modifier.isStatic(field.getModifiers())) {
+ continue;
+ }
+ field.setAccessible(true);
+ if (field.get(null) instanceof Boolean enabled && enabled) {
+ enabledFields.add(field);
+ }
+ }
+ }
+
+ private static List formatFields(List fields) {
+ return fields.stream()
+ .map(field -> field.getDeclaringClass().getSimpleName() + "." + field.getName())
+ .toList();
+ }
+}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java
index 5db0e40c12..651cbcb41e 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java
@@ -117,8 +117,10 @@ private static void loadConfig(boolean init) throws Exception {
globalConfig.saveConfig();
Files.copy(new File(CONFIG_DIRECTORY, GLOBAL_CONFIG_FILE).toPath(), worldDefaultsFile.toPath());
}
- worldDefaultsConfig = LeafWorldConfig.loadDefaults(worldDefaultsFile);
+ LeafWorldConfig previousWorldDefaults = worldDefaultsConfig;
+ worldDefaultsConfig = LeafWorldConfig.loadDefaults(worldDefaultsFile, previousWorldDefaults);
worldDefaultsConfig.saveConfig();
+ ConfigModuleLoader.markInitialized();
}
public static LeafGlobalConfig globalConfig() {
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigAccessor.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigAccessor.java
new file mode 100644
index 0000000000..85ac8a57fd
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigAccessor.java
@@ -0,0 +1,177 @@
+package org.dreeam.leaf.config;
+
+import io.github.thatsmusic99.configurationmaster.api.ConfigFile;
+import io.github.thatsmusic99.configurationmaster.api.ConfigSection;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+/** Shared configuration-file utilities for global and world configuration views. */
+abstract class LeafConfigAccessor {
+
+ protected final ConfigFile configFile;
+
+ protected LeafConfigAccessor(File file) throws Exception {
+ this.configFile = ConfigFile.loadConfig(file);
+ }
+
+ public void saveConfig() throws Exception {
+ configFile.save();
+ }
+
+ public boolean migratePath(String oldPath, String newPath) {
+ return ConfigPathMigration.migrate(configFile, oldPath, newPath);
+ }
+
+ public void createTitledSection(String title, String path) {
+ configFile.addSection(title);
+ configFile.addDefault(path, null);
+ }
+
+ public boolean getBoolean(String path, boolean def, String comment) {
+ configFile.addDefault(path, def, comment);
+ return configFile.getBoolean(path, def);
+ }
+
+ public boolean getBoolean(String path, boolean def) {
+ configFile.addDefault(path, def);
+ return configFile.getBoolean(path, def);
+ }
+
+ public String getString(String path, String def, String comment) {
+ configFile.addDefault(path, def, comment);
+ return configFile.getString(path, def);
+ }
+
+ public String getString(String path, String def) {
+ configFile.addDefault(path, def);
+ return configFile.getString(path, def);
+ }
+
+ public double getDouble(String path, double def, String comment) {
+ configFile.addDefault(path, def, comment);
+ return configFile.getDouble(path, def);
+ }
+
+ public double getDouble(String path, double def) {
+ configFile.addDefault(path, def);
+ return configFile.getDouble(path, def);
+ }
+
+ public int getInt(String path, int def, String comment) {
+ configFile.addDefault(path, def, comment);
+ return configFile.getInteger(path, def);
+ }
+
+ public int getInt(String path, int def) {
+ configFile.addDefault(path, def);
+ return configFile.getInteger(path, def);
+ }
+
+ public long getLong(String path, long def, String comment) {
+ configFile.addDefault(path, def, comment);
+ return configFile.getLong(path, def);
+ }
+
+ public long getLong(String path, long def) {
+ configFile.addDefault(path, def);
+ return configFile.getLong(path, def);
+ }
+
+ public List getList(String path, List def, String comment) {
+ configFile.addDefault(path, def, comment);
+ return configFile.getStringList(path);
+ }
+
+ public List getList(String path, List def) {
+ configFile.addDefault(path, def);
+ return configFile.getStringList(path);
+ }
+
+ public ConfigSection getConfigSection(String path, Map defaultKeyValue, String comment) {
+ configFile.addDefault(path, null, comment);
+ configFile.makeSectionLenient(path);
+ defaultKeyValue.forEach((key, value) -> configFile.addExample(path + "." + key, value));
+ return configFile.getConfigSection(path);
+ }
+
+ public ConfigSection getConfigSection(String path, Map defaultKeyValue) {
+ configFile.addDefault(path, null);
+ configFile.makeSectionLenient(path);
+ defaultKeyValue.forEach((key, value) -> configFile.addExample(path + "." + key, value));
+ return configFile.getConfigSection(path);
+ }
+
+ public Boolean getBoolean(String path) {
+ String value = configFile.getString(path, null);
+ return value == null ? null : Boolean.parseBoolean(value);
+ }
+
+ public String getString(String path) {
+ return configFile.getString(path, null);
+ }
+
+ public Double getDouble(String path) {
+ String value = configFile.getString(path, null);
+ if (value == null) return null;
+ try {
+ return Double.parseDouble(value);
+ } catch (NumberFormatException exception) {
+ LeafConfig.LOGGER.warn("{} is not a valid number, skipped! Please check your configuration.", path, exception);
+ return null;
+ }
+ }
+
+ public Integer getInt(String path) {
+ String value = configFile.getString(path, null);
+ if (value == null) return null;
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException exception) {
+ LeafConfig.LOGGER.warn("{} is not a valid number, skipped! Please check your configuration.", path, exception);
+ return null;
+ }
+ }
+
+ public Long getLong(String path) {
+ String value = configFile.getString(path, null);
+ if (value == null) return null;
+ try {
+ return Long.parseLong(value);
+ } catch (NumberFormatException exception) {
+ LeafConfig.LOGGER.warn("{} is not a valid number, skipped! Please check your configuration.", path, exception);
+ return null;
+ }
+ }
+
+ public List getList(String path) {
+ return configFile.getList(path, null);
+ }
+
+ public ConfigSection getConfigSection(String path) {
+ configFile.addDefault(path, null);
+ configFile.makeSectionLenient(path);
+ return configFile.getConfigSection(path);
+ }
+
+ public void addComment(String path, String comment) {
+ configFile.addComment(path, comment);
+ }
+
+ public void addCommentIfCN(String path, String comment) {
+ if (LeafConfig.isChineseLocale()) configFile.addComment(path, comment);
+ }
+
+ public void addCommentIfNonCN(String path, String comment) {
+ if (!LeafConfig.isChineseLocale()) configFile.addComment(path, comment);
+ }
+
+ public void addCommentRegionBased(String path, String en, String cn) {
+ configFile.addComment(path, LeafConfig.isChineseLocale() ? cn : en);
+ }
+
+ public String pickStringRegionBased(String en, String cn) {
+ return LeafConfig.isChineseLocale() ? cn : en;
+ }
+}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafGlobalConfig.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafGlobalConfig.java
index c9ff528323..84785e84d1 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafGlobalConfig.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafGlobalConfig.java
@@ -1,23 +1,16 @@
package org.dreeam.leaf.config;
-import io.github.thatsmusic99.configurationmaster.api.ConfigFile;
-import io.github.thatsmusic99.configurationmaster.api.ConfigSection;
-
import java.io.File;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-
-public class LeafGlobalConfig {
- protected final ConfigFile configFile;
+/** The server-wide Leaf configuration. */
+public final class LeafGlobalConfig extends LeafConfigAccessor {
public LeafGlobalConfig(boolean init) throws Exception {
this(new File(LeafConfig.CONFIG_DIRECTORY, LeafConfig.GLOBAL_CONFIG_FILE), true);
}
- protected LeafGlobalConfig(File file, boolean loadConfigVersion) throws Exception {
- configFile = ConfigFile.loadConfig(file);
+ LeafGlobalConfig(File file, boolean loadConfigVersion) throws Exception {
+ super(file);
if (loadConfigVersion) {
LeafConfig.loadPreviousConfigVersion(getString("config-version"));
@@ -39,195 +32,12 @@ protected LeafGlobalConfig(File file, boolean loadConfigVersion) throws Exceptio
GitHub 仓库: https://github.com/Winds-Studio/Leaf
QQ社区群: 619278377"""));
- // Pre-structure to force order
structureConfig();
}
- protected void structureConfig() {
- for (ConfigCategory category : ConfigCategory.categoryValues()) {
+ private void structureConfig() {
+ for (ConfigCategory category : ConfigCategory.values()) {
createTitledSection(category.name(), category.basePath());
}
}
-
- public void saveConfig() throws Exception {
- configFile.save();
- }
-
- /**
- * Moves a deprecated option path to its replacement.
- *
- * @see ConfigPathMigration#migrate(ConfigSection, String, String)
- */
- public boolean migratePath(String oldPath, String newPath) {
- return ConfigPathMigration.migrate(configFile, oldPath, newPath);
- }
-
- // Config Utilities
-
- /* getAndSet */
-
- public void createTitledSection(String title, String path) {
- configFile.addSection(title);
- configFile.addDefault(path, null);
- }
-
- public boolean getBoolean(String path, boolean def, String comment) {
- configFile.addDefault(path, def, comment);
- return configFile.getBoolean(path, def);
- }
-
- public boolean getBoolean(String path, boolean def) {
- configFile.addDefault(path, def);
- return configFile.getBoolean(path, def);
- }
-
- public String getString(String path, String def, String comment) {
- configFile.addDefault(path, def, comment);
- return configFile.getString(path, def);
- }
-
- public String getString(String path, String def) {
- configFile.addDefault(path, def);
- return configFile.getString(path, def);
- }
-
- public double getDouble(String path, double def, String comment) {
- configFile.addDefault(path, def, comment);
- return configFile.getDouble(path, def);
- }
-
- public double getDouble(String path, double def) {
- configFile.addDefault(path, def);
- return configFile.getDouble(path, def);
- }
-
- public int getInt(String path, int def, String comment) {
- configFile.addDefault(path, def, comment);
- return configFile.getInteger(path, def);
- }
-
- public int getInt(String path, int def) {
- configFile.addDefault(path, def);
- return configFile.getInteger(path, def);
- }
-
- public long getLong(String path, long def, String comment) {
- configFile.addDefault(path, def, comment);
- return configFile.getLong(path, def);
- }
-
- public long getLong(String path, long def) {
- configFile.addDefault(path, def);
- return configFile.getLong(path, def);
- }
-
- public List getList(String path, List def, String comment) {
- configFile.addDefault(path, def, comment);
- return configFile.getStringList(path);
- }
-
- public List getList(String path, List def) {
- configFile.addDefault(path, def);
- return configFile.getStringList(path);
- }
-
- public ConfigSection getConfigSection(String path, Map defaultKeyValue, String comment) {
- configFile.addDefault(path, null, comment);
- configFile.makeSectionLenient(path);
- defaultKeyValue.forEach((string, object) -> configFile.addExample(path + "." + string, object));
- return configFile.getConfigSection(path);
- }
-
- public ConfigSection getConfigSection(String path, Map defaultKeyValue) {
- configFile.addDefault(path, null);
- configFile.makeSectionLenient(path);
- defaultKeyValue.forEach((string, object) -> configFile.addExample(path + "." + string, object));
- return configFile.getConfigSection(path);
- }
-
- /* get */
-
- public Boolean getBoolean(String path) {
- String value = configFile.getString(path, null);
- return value == null ? null : Boolean.parseBoolean(value);
- }
-
- public String getString(String path) {
- return configFile.getString(path, null);
- }
-
- public Double getDouble(String path) {
- String value = configFile.getString(path, null);
- if (value == null) {
- return null;
- }
- try {
- return Double.parseDouble(value);
- } catch (NumberFormatException e) {
- LeafConfig.LOGGER.warn("{} is not a valid number, skipped! Please check your configuration.", path, e);
- return null;
- }
- }
-
- public Integer getInt(String path) {
- String value = configFile.getString(path, null);
- if (value == null) {
- return null;
- }
- try {
- return Integer.parseInt(value);
- } catch (NumberFormatException e) {
- LeafConfig.LOGGER.warn("{} is not a valid number, skipped! Please check your configuration.", path, e);
- return null;
- }
- }
-
- public Long getLong(String path) {
- String value = configFile.getString(path, null);
- if (value == null) {
- return null;
- }
- try {
- return Long.parseLong(value);
- } catch (NumberFormatException e) {
- LeafConfig.LOGGER.warn("{} is not a valid number, skipped! Please check your configuration.", path, e);
- return null;
- }
- }
-
- public List getList(String path) {
- return configFile.getList(path, null);
- }
-
- // TODO, check
- public ConfigSection getConfigSection(String path) {
- configFile.addDefault(path, null);
- configFile.makeSectionLenient(path);
- //defaultKeyValue.forEach((string, object) -> configFile.addExample(path + "." + string, object));
- return configFile.getConfigSection(path);
- }
-
- public void addComment(String path, String comment) {
- configFile.addComment(path, comment);
- }
-
- public void addCommentIfCN(String path, String comment) {
- if (LeafConfig.isChineseLocale()) {
- configFile.addComment(path, comment);
- }
- }
-
- public void addCommentIfNonCN(String path, String comment) {
- if (!LeafConfig.isChineseLocale()) {
- configFile.addComment(path, comment);
- }
- }
-
- public void addCommentRegionBased(String path, String en, String cn) {
- configFile.addComment(path, LeafConfig.isChineseLocale() ? cn : en);
- }
-
- public String pickStringRegionBased(String en, String cn) {
- return LeafConfig.isChineseLocale() ? cn : en;
- }
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafWorldConfig.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafWorldConfig.java
index 5fdcab25b4..7a479caf2e 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafWorldConfig.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafWorldConfig.java
@@ -3,6 +3,7 @@
import io.github.thatsmusic99.configurationmaster.api.ConfigSection;
import java.io.File;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -12,141 +13,182 @@
* The file is never created by this class. Callers must check {@link #exists()} before
* constructing it, so worlds without {@code leaf-world.yml} use the shared defaults directly.
*/
-public final class LeafWorldConfig extends LeafGlobalConfig {
+public final class LeafWorldConfig extends LeafConfigAccessor {
- private final LeafGlobalConfig defaults;
+ private final LeafWorldConfig defaults;
+ private LeafWorldConfig reloadSource;
+ private final Map, WorldConfigModule> modules = new LinkedHashMap<>();
public boolean secureSeedEnabled;
public static LeafWorldConfig loadDefaults(File file) throws Exception {
- return new LeafWorldConfig(file, null);
+ return loadDefaults(file, null);
}
- public LeafWorldConfig(File file, LeafGlobalConfig defaults) throws Exception {
- super(file, false);
+ static LeafWorldConfig loadDefaults(File file, LeafWorldConfig reloadSource) throws Exception {
+ return new LeafWorldConfig(file, null, reloadSource);
+ }
+
+ public LeafWorldConfig(File file, LeafWorldConfig defaults) throws Exception {
+ this(file, defaults, null);
+ }
+
+ private LeafWorldConfig(File file, LeafWorldConfig defaults, LeafWorldConfig reloadSource) throws Exception {
+ super(file);
this.defaults = defaults;
- ConfigModule.loadWorldModules(this);
+ this.reloadSource = reloadSource;
+ try {
+ ConfigModule.loadWorldModules(this);
+ } finally {
+ this.reloadSource = null;
+ }
}
public static boolean exists(File file) {
return file.isFile();
}
- @Override
- protected void structureConfig() {
- // World files are override-only and must not be populated with the defaults structure.
- }
-
- private boolean overrides(String path) {
- return this.defaults == null || this.configFile.contains(path);
+ private boolean usesWorldDefaults(String path) {
+ return this.defaults != null && !this.configFile.contains(path);
}
public boolean isDefaultsConfig() {
return this.defaults == null;
}
+ boolean isReload() {
+ return this.reloadSource != null;
+ }
+
+ /**
+ * Returns this world's annotation-driven module instance.
+ */
+ public T getModule(Class moduleClass) {
+ WorldConfigModule module = this.modules.get(moduleClass);
+ if (module == null) {
+ throw new IllegalArgumentException("World configuration module is not registered: "
+ + moduleClass.getName());
+ }
+ return moduleClass.cast(module);
+ }
+
+ void registerModule(Class moduleClass, T module) {
+ WorldConfigModule previousModule = this.modules.putIfAbsent(moduleClass, module);
+ if (previousModule != null) {
+ throw new IllegalStateException("Duplicate world configuration module: " + moduleClass.getName());
+ }
+ }
+
+ T reloadModule(Class moduleClass) {
+ if (this.reloadSource == null) {
+ return null;
+ }
+ WorldConfigModule module = this.reloadSource.modules.get(moduleClass);
+ return module == null ? null : moduleClass.cast(module);
+ }
+
@Override
public boolean getBoolean(String path, boolean def, String comment) {
- return overrides(path) ? super.getBoolean(path, def, comment) : defaults.getBoolean(path, def, comment);
+ return usesWorldDefaults(path) ? defaults.getBoolean(path, def, comment) : super.getBoolean(path, def, comment);
}
@Override
public boolean getBoolean(String path, boolean def) {
- return overrides(path) ? super.getBoolean(path, def) : defaults.getBoolean(path, def);
+ return usesWorldDefaults(path) ? defaults.getBoolean(path, def) : super.getBoolean(path, def);
}
@Override
public String getString(String path, String def, String comment) {
- return overrides(path) ? super.getString(path, def, comment) : defaults.getString(path, def, comment);
+ return usesWorldDefaults(path) ? defaults.getString(path, def, comment) : super.getString(path, def, comment);
}
@Override
public String getString(String path, String def) {
- return overrides(path) ? super.getString(path, def) : defaults.getString(path, def);
+ return usesWorldDefaults(path) ? defaults.getString(path, def) : super.getString(path, def);
}
@Override
public double getDouble(String path, double def, String comment) {
- return overrides(path) ? super.getDouble(path, def, comment) : defaults.getDouble(path, def, comment);
+ return usesWorldDefaults(path) ? defaults.getDouble(path, def, comment) : super.getDouble(path, def, comment);
}
@Override
public double getDouble(String path, double def) {
- return overrides(path) ? super.getDouble(path, def) : defaults.getDouble(path, def);
+ return usesWorldDefaults(path) ? defaults.getDouble(path, def) : super.getDouble(path, def);
}
@Override
public int getInt(String path, int def, String comment) {
- return overrides(path) ? super.getInt(path, def, comment) : defaults.getInt(path, def, comment);
+ return usesWorldDefaults(path) ? defaults.getInt(path, def, comment) : super.getInt(path, def, comment);
}
@Override
public int getInt(String path, int def) {
- return overrides(path) ? super.getInt(path, def) : defaults.getInt(path, def);
+ return usesWorldDefaults(path) ? defaults.getInt(path, def) : super.getInt(path, def);
}
@Override
public long getLong(String path, long def, String comment) {
- return overrides(path) ? super.getLong(path, def, comment) : defaults.getLong(path, def, comment);
+ return usesWorldDefaults(path) ? defaults.getLong(path, def, comment) : super.getLong(path, def, comment);
}
@Override
public long getLong(String path, long def) {
- return overrides(path) ? super.getLong(path, def) : defaults.getLong(path, def);
+ return usesWorldDefaults(path) ? defaults.getLong(path, def) : super.getLong(path, def);
}
@Override
public List getList(String path, List def, String comment) {
- return overrides(path) ? super.getList(path, def, comment) : defaults.getList(path, def, comment);
+ return usesWorldDefaults(path) ? defaults.getList(path, def, comment) : super.getList(path, def, comment);
}
@Override
public List getList(String path, List def) {
- return overrides(path) ? super.getList(path, def) : defaults.getList(path, def);
+ return usesWorldDefaults(path) ? defaults.getList(path, def) : super.getList(path, def);
}
@Override
public ConfigSection getConfigSection(String path, Map values, String comment) {
- return overrides(path) ? super.getConfigSection(path, values, comment) : defaults.getConfigSection(path, values, comment);
+ return usesWorldDefaults(path) ? defaults.getConfigSection(path, values, comment) : super.getConfigSection(path, values, comment);
}
@Override
public ConfigSection getConfigSection(String path, Map values) {
- return overrides(path) ? super.getConfigSection(path, values) : defaults.getConfigSection(path, values);
+ return usesWorldDefaults(path) ? defaults.getConfigSection(path, values) : super.getConfigSection(path, values);
}
@Override
public Boolean getBoolean(String path) {
- return overrides(path) ? super.getBoolean(path) : defaults.getBoolean(path);
+ return usesWorldDefaults(path) ? defaults.getBoolean(path) : super.getBoolean(path);
}
@Override
public String getString(String path) {
- return overrides(path) ? super.getString(path) : defaults.getString(path);
+ return usesWorldDefaults(path) ? defaults.getString(path) : super.getString(path);
}
@Override
public Double getDouble(String path) {
- return overrides(path) ? super.getDouble(path) : defaults.getDouble(path);
+ return usesWorldDefaults(path) ? defaults.getDouble(path) : super.getDouble(path);
}
@Override
public Integer getInt(String path) {
- return overrides(path) ? super.getInt(path) : defaults.getInt(path);
+ return usesWorldDefaults(path) ? defaults.getInt(path) : super.getInt(path);
}
@Override
public Long getLong(String path) {
- return overrides(path) ? super.getLong(path) : defaults.getLong(path);
+ return usesWorldDefaults(path) ? defaults.getLong(path) : super.getLong(path);
}
@Override
public List getList(String path) {
- return overrides(path) ? super.getList(path) : defaults.getList(path);
+ return usesWorldDefaults(path) ? defaults.getList(path) : super.getList(path);
}
@Override
public ConfigSection getConfigSection(String path) {
- return overrides(path) ? super.getConfigSection(path) : defaults.getConfigSection(path);
+ return usesWorldDefaults(path) ? defaults.getConfigSection(path) : super.getConfigSection(path);
}
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/WorldConfigModule.java b/leaf-server/src/main/java/org/dreeam/leaf/config/WorldConfigModule.java
index 09c61b6ce9..a584827ab0 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/WorldConfigModule.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/WorldConfigModule.java
@@ -1,7 +1,10 @@
package org.dreeam.leaf.config;
-/** Loads a module's effective values from a world-defaults/override configuration view. */
-public interface WorldConfigModule {
-
- void loadWorldConfig(LeafWorldConfig config);
+/**
+ * Marker and lifecycle contract for a world-scoped Leaf configuration module.
+ *
+ * Annotated fields must be mutable instance fields. A separate module instance is created
+ * for the defaults and for every world override.
+ */
+public interface WorldConfigModule extends ConfigModule {
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/ConfigClassInfo.java b/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/ConfigClassInfo.java
new file mode 100644
index 0000000000..c429c9fbde
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/ConfigClassInfo.java
@@ -0,0 +1,23 @@
+package org.dreeam.leaf.config.annotations;
+
+import org.dreeam.leaf.config.EnumConfigCategory;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface ConfigClassInfo {
+
+ EnumConfigCategory category();
+
+ String name();
+
+ String[] directory() default {};
+
+ String comments() default "";
+}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/ConfigInfo.java b/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/ConfigInfo.java
new file mode 100644
index 0000000000..564e0bd5de
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/ConfigInfo.java
@@ -0,0 +1,19 @@
+package org.dreeam.leaf.config.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface ConfigInfo {
+
+ String name();
+
+ String[] directory() default {};
+
+ String comments() default "";
+}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/DoNotLoad.java b/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/DoNotLoad.java
index b6687584b7..cd5f7da6bc 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/DoNotLoad.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/DoNotLoad.java
@@ -1,8 +1,16 @@
package org.dreeam.leaf.config.annotations;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+/**
+ * Marks a runtime-only or derived field that must never be read from or written to configuration.
+ */
+@Documented
@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
public @interface DoNotLoad {
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/HotReloadUnsupported.java b/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/HotReloadUnsupported.java
index c89bf6a7ec..c4dbdcf1c1 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/HotReloadUnsupported.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/HotReloadUnsupported.java
@@ -1,8 +1,14 @@
package org.dreeam.leaf.config.annotations;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+/** Keeps an annotated configuration field unchanged during a hot reload. */
+@Documented
@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
public @interface HotReloadUnsupported {
}
From 6759f24978dc66567e8a4c07f60631f2b093b8ff Mon Sep 17 00:00:00 2001
From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com>
Date: Wed, 12 Aug 2026 09:07:11 +0800
Subject: [PATCH 03/14] Temp reduce diff
---
.../features/0004-Leaf-config.patch | 65 -----------------
.../0011-Move-random-tick-random.patch | 8 +--
...timize-random-calls-in-chunk-ticking.patch | 4 +-
...017-Remove-lambda-from-ticking-guard.patch | 8 +--
.../0042-Reduce-array-allocations.patch | 10 +--
...block-destruction-packet-allocations.patch | 8 +--
...fferfish-Dynamic-Activation-of-Brain.patch | 2 +-
.../features/0100-Leaves-Replay-Mod-API.patch | 8 +--
.../features/0103-Reduce-canSee-work.patch | 4 +-
.../features/0119-Matter-Secure-Seed.patch | 4 +-
.../0121-Faster-random-generator.patch | 12 ++--
...e-stream-in-CraftWorld-spawnParticle.patch | 6 +-
.../features/0174-Cache-chunk-key.patch | 6 +-
...117075-Block-Entities-Unload-Lag-Spi.patch | 6 +-
.../features/0184-optimize-mob-despawn.patch | 8 +--
...-SparklyPaper-Parallel-world-ticking.patch | 38 +++++-----
...celled-Projectile-Events-still-consu.patch | 4 +-
.../0197-Use-BFS-on-getSlopeDistance.patch | 4 +-
...00-Raytrace-AntiXray-SDK-integration.patch | 4 +-
.../features/0227-optimize-mob-spawning.patch | 6 +-
.../features/0234-optimize-random-tick.patch | 4 +-
.../features/0239-Paw-optimization.patch | 6 +-
...Paper-PR-Optimise-temptation-lookups.patch | 6 +-
...-Optimise-temptation-lookups-changes.patch | 2 +-
.../0255-thread-unsafe-chunk-map.patch | 4 +-
.../features/0257-optimize-get-chunk.patch | 4 +-
...0258-remove-shouldTickBlocksAt-check.patch | 4 +-
.../0262-optimize-fluid-state-access.patch | 4 +-
.../features/0267-cache-collision-list.patch | 2 +-
.../features/0268-fast-bit-radix-sort.patch | 4 +-
...Pluto-Expose-Direction-Plane-s-faces.patch | 8 +--
.../features/0275-Multithreaded-Tracker.patch | 2 +-
.../0277-Rewrite-entity-despawn-time.patch | 8 +--
.../features/0279-Cache-world-border.patch | 4 +-
...andomTick-new-BlockPos-instance-crea.patch | 4 +-
...onfigurable-ice-and-snow-tick-chance.patch | 4 +-
.../0302-disable-world-data-saving.patch | 6 +-
...Leaves-Lithium-Sleeping-Block-Entity.patch | 8 +--
...Leaves-Lithium-Sleeping-Block-Entity.patch | 4 +-
...309-Add-read-only-mode-for-Linear-v2.patch | 6 +-
.../0321-optimize-entity-activation.patch | 6 +-
.../0326-Limit-pushable-entity-queries.patch | 4 +-
.../features/0327-Leaf-config-changes.patch | 71 +++++++++++++++++++
43 files changed, 198 insertions(+), 192 deletions(-)
create mode 100644 leaf-server/minecraft-patches/features/0327-Leaf-config-changes.patch
diff --git a/leaf-server/minecraft-patches/features/0004-Leaf-config.patch b/leaf-server/minecraft-patches/features/0004-Leaf-config.patch
index aee58e2f69..e6fb313404 100644
--- a/leaf-server/minecraft-patches/features/0004-Leaf-config.patch
+++ b/leaf-server/minecraft-patches/features/0004-Leaf-config.patch
@@ -39,68 +39,3 @@ index bc2c23301695627ea5cdea618be3e4e3bae57f5e..ca5c50fc933e78c2c97adce2aca77793
// Paper start - Add onboarding message for initial server start
if (io.papermc.paper.configuration.GlobalConfiguration.isFirstStart) {
LOGGER.info("*************************************************************************************");
-diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 745ec876080331f27f3eabf466ff914d39c8d2c4..164e8a1699755d8e7e756b0f322eca199e371b81 100644
---- a/net/minecraft/server/level/ServerLevel.java
-+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -635,7 +635,24 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
- savedDataStorage.set(io.papermc.paper.world.saveddata.PaperWorldPDC.TYPE, loadedWorldData.pdc() == null ? io.papermc.paper.world.saveddata.PaperWorldPDC.TYPE.constructor().get() : loadedWorldData.pdc());
- final GameRules gameRules = new GameRules(server.getWorldData().enabledFeatures(), savedDataStorage.computeIfAbsent(net.minecraft.world.level.gamerules.GameRuleMap.TYPE));
- this.gameRules = gameRules;
-- super(levelData, dimension, server.registryAccess(), levelStem.type(), false, isDebug, biomeZoomSeed, server.getMaxChainedNeighborUpdates(), loadedWorldData.bukkitName(), gen, biomeProvider, env, spigotConfig -> server.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(server.storageSource.getDimensionPath(dimension), dimension.identifier(), spigotConfig, server.registryAccess(), gameRules)), spigotConfig -> server.galeConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(server.storageSource.getDimensionPath(dimension), dimension.identifier(), spigotConfig, server.registryAccess(), gameRules)), executor); // Paper - create paper world configs // Paper - Anti-Xray - Pass executor // Gale - Gale configuration
-+ super(
-+ levelData,
-+ dimension,
-+ server.registryAccess(),
-+ levelStem.type(),
-+ false,
-+ isDebug,
-+ biomeZoomSeed,
-+ server.getMaxChainedNeighborUpdates(),
-+ loadedWorldData.bukkitName(),
-+ gen,
-+ biomeProvider,
-+ env,
-+ spigotConfig -> server.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(server.storageSource.getDimensionPath(dimension), dimension.identifier(), spigotConfig, server.registryAccess(), gameRules)), // Paper - create paper world configs
-+ spigotConfig -> server.galeConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(server.storageSource.getDimensionPath(dimension), dimension.identifier(), spigotConfig, server.registryAccess(), gameRules)), // Gale - Gale configuration
-+ spigotConfig -> org.dreeam.leaf.config.LeafConfig.createWorldConfig(server.storageSource.getDimensionPath(dimension)), // Leaf - per-world configuration
-+ executor
-+ );
- this.weatherData = savedDataStorage.computeIfAbsent(WeatherData.TYPE);
- this.weatherData.setLevel(this);
- this.typeKey = typeKey;
-diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 96eccf7045781e25c8e6f4c2a391cd7d44672bf7..2ddc5f0105aab50f24580609ad1ea6c441d51ef9 100644
---- a/net/minecraft/world/level/Level.java
-+++ b/net/minecraft/world/level/Level.java
-@@ -179,6 +179,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
- }
- // Gale end - Gale configuration
-
-+ // Leaf start - per-world configuration
-+ private final org.dreeam.leaf.config.LeafWorldConfig leafConfig;
-+ public org.dreeam.leaf.config.LeafWorldConfig leafConfig() {
-+ return this.leafConfig;
-+ }
-+ // Leaf end - per-world configuration
-+
- public final org.purpurmc.purpur.PurpurWorldConfig purpurConfig; // Purpur - Purpur config files
- public static @Nullable BlockPos lastPhysicsProblem; // Spigot
- public final Map explosionDensityCache = new java.util.HashMap<>(); // Paper - Optimize explosions
-@@ -892,6 +899,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
- io.papermc.paper.configuration.WorldConfiguration> paperWorldConfigCreator, // Paper - create paper world config
- java.util.function.Function galeWorldConfigCreator, // Gale - Gale configuration
-+ java.util.function.Function leafWorldConfigCreator, // Leaf - per-world configuration
- java.util.concurrent.Executor executor // Paper - Anti-Xray
- ) {
- // Paper start - getblock optimisations - cache world height/sections
-@@ -908,6 +917,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
- this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config
- this.purpurConfig = new org.purpurmc.purpur.PurpurWorldConfig(bukkitName, environment, worldKey); // Purpur - Purpur config files
- this.galeConfig = galeWorldConfigCreator.apply(this.spigotConfig); // Gale - Gale configuration
-+ this.leafConfig = leafWorldConfigCreator.apply(this.spigotConfig); // Leaf - per-world configuration
- this.playerBreedingCooldowns = this.getNewBreedingCooldownCache(); // Purpur - Add adjustable breeding cooldown to config
- this.generator = generator;
- this.world = new CraftWorld((ServerLevel) this, worldKey, biomeProvider, environment);
diff --git a/leaf-server/minecraft-patches/features/0011-Move-random-tick-random.patch b/leaf-server/minecraft-patches/features/0011-Move-random-tick-random.patch
index 8d7bc46246..ecc391442a 100644
--- a/leaf-server/minecraft-patches/features/0011-Move-random-tick-random.patch
+++ b/leaf-server/minecraft-patches/features/0011-Move-random-tick-random.patch
@@ -19,10 +19,10 @@ require it to be initialized earlier. By moving it to the superclass, we
initialize it earlier, ensuring that it is available sooner.
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 164e8a1699755d8e7e756b0f322eca199e371b81..67b687dfde8a5fcc9e2223c91c36a25cc533f99e 100644
+index d22b619eb47cd99b7d3d44bf0fa5c6278bbaaf6a..16a0f7ede6924fee0019c6b054b96a2eaf68677d 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -996,8 +996,6 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -979,8 +979,6 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
}
// Paper start - optimise random ticking
@@ -32,10 +32,10 @@ index 164e8a1699755d8e7e756b0f322eca199e371b81..67b687dfde8a5fcc9e2223c91c36a25c
final LevelChunkSection[] sections = chunk.getSections();
final int minSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection((ServerLevel)(Object)this);
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 2ddc5f0105aab50f24580609ad1ea6c441d51ef9..f4b0cbccab1e24587f13f451abb00c9eb1f4bae8 100644
+index 96eccf7045781e25c8e6f4c2a391cd7d44672bf7..6ccf3d9cf5e048602f2f9d73bdb5ed73e7ac5a5b 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
-@@ -190,6 +190,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -183,6 +183,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
public static @Nullable BlockPos lastPhysicsProblem; // Spigot
public final Map explosionDensityCache = new java.util.HashMap<>(); // Paper - Optimize explosions
public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Faster redstone torch rapid clock removal; Move from Map in BlockRedstoneTorch to here
diff --git a/leaf-server/minecraft-patches/features/0012-Optimize-random-calls-in-chunk-ticking.patch b/leaf-server/minecraft-patches/features/0012-Optimize-random-calls-in-chunk-ticking.patch
index bfcd1c3b46..0089c136c2 100644
--- a/leaf-server/minecraft-patches/features/0012-Optimize-random-calls-in-chunk-ticking.patch
+++ b/leaf-server/minecraft-patches/features/0012-Optimize-random-calls-in-chunk-ticking.patch
@@ -57,10 +57,10 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 67b687dfde8a5fcc9e2223c91c36a25cc533f99e..b37c53aa74577b2ae5705875199c65737ec35b11 100644
+index 16a0f7ede6924fee0019c6b054b96a2eaf68677d..7952d6836743b6ed88221503c905d5c0dfa56e82 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1076,7 +1076,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1059,7 +1059,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
int minZ = chunkPos.getMinBlockZ();
ProfilerFiller profiler = Profiler.get();
profiler.push("thunder");
diff --git a/leaf-server/minecraft-patches/features/0017-Remove-lambda-from-ticking-guard.patch b/leaf-server/minecraft-patches/features/0017-Remove-lambda-from-ticking-guard.patch
index 0b109016ef..6959966b9a 100644
--- a/leaf-server/minecraft-patches/features/0017-Remove-lambda-from-ticking-guard.patch
+++ b/leaf-server/minecraft-patches/features/0017-Remove-lambda-from-ticking-guard.patch
@@ -33,10 +33,10 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index b37c53aa74577b2ae5705875199c65737ec35b11..478a3b9ad4738e789b425ec57192fce111126dcd 100644
+index 7952d6836743b6ed88221503c905d5c0dfa56e82..a02889c57217422b6231b9708ed8f7cb423da27c 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -936,7 +936,19 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -919,7 +919,19 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
}
profiler.push("tick");
@@ -58,10 +58,10 @@ index b37c53aa74577b2ae5705875199c65737ec35b11..478a3b9ad4738e789b425ec57192fce1
}
}
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index f4b0cbccab1e24587f13f451abb00c9eb1f4bae8..499a79d5592a6ff5bf74ca78dd3a7d70809d195d 100644
+index 6ccf3d9cf5e048602f2f9d73bdb5ed73e7ac5a5b..7ef63a242b0d32f6f6d1a95d7331f36921fbd029 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
-@@ -1592,10 +1592,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1582,10 +1582,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
final String msg = String.format("Entity threw exception at %s:%s,%s,%s", io.papermc.paper.util.MCUtil.getLevelName(entity.level()), entity.getX(), entity.getY(), entity.getZ());
MinecraftServer.LOGGER.error(msg, t);
getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerInternalException(msg, t))); // Paper - ServerExceptionEvent
diff --git a/leaf-server/minecraft-patches/features/0042-Reduce-array-allocations.patch b/leaf-server/minecraft-patches/features/0042-Reduce-array-allocations.patch
index f145df4b3d..eb25baa504 100644
--- a/leaf-server/minecraft-patches/features/0042-Reduce-array-allocations.patch
+++ b/leaf-server/minecraft-patches/features/0042-Reduce-array-allocations.patch
@@ -137,10 +137,10 @@ index d7e5e8541fde613a5b82da59a1e2b95e0497fe61..bbcb768f587305223c67d30186d62e70
if (!itemStack.isEmpty()) {
slots.add(Pair.of(slot, itemStack.copy()));
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 38d35ce1b82a0dd311a6d16b55a8c7b55bb8784a..e36e18d0ad4efb648f43ba8dc78ddaca5ba2c0d5 100644
+index a02889c57217422b6231b9708ed8f7cb423da27c..80225959a9f599b05c0bf0d13455dba45a0c8983 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1479,7 +1479,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1462,7 +1462,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
public static List getCurrentlyTickingEntities() {
Entity ticking = currentlyTickingEntity.get();
@@ -441,10 +441,10 @@ index 470104cec5a78ea4c68cce664590c9af96500829..10be004c98d608327c18fb07c3e9d114
// do not even check enchantments for item with lower or equal damage percent
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 499a79d5592a6ff5bf74ca78dd3a7d70809d195d..d64c2837267958a1226b9216da587ca58770a600 100644
+index 7ef63a242b0d32f6f6d1a95d7331f36921fbd029..734a8fe92417aff18fb3964df2c90f46e054c86b 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
-@@ -1933,7 +1933,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1923,7 +1923,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
public org.bukkit.entity.Entity[] getChunkEntities(int chunkX, int chunkZ) {
ca.spottedleaf.moonrise.patches.chunk_system.level.entity.ChunkEntitySlices slices = ((ServerLevel)this).moonrise$getEntityLookup().getChunk(chunkX, chunkZ);
if (slices == null) {
@@ -453,7 +453,7 @@ index 499a79d5592a6ff5bf74ca78dd3a7d70809d195d..d64c2837267958a1226b9216da587ca5
}
List ret = new java.util.ArrayList<>();
-@@ -1944,7 +1944,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1934,7 +1934,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
}
}
diff --git a/leaf-server/minecraft-patches/features/0060-Reduce-block-destruction-packet-allocations.patch b/leaf-server/minecraft-patches/features/0060-Reduce-block-destruction-packet-allocations.patch
index 948e6e3ff5..896cedded3 100644
--- a/leaf-server/minecraft-patches/features/0060-Reduce-block-destruction-packet-allocations.patch
+++ b/leaf-server/minecraft-patches/features/0060-Reduce-block-destruction-packet-allocations.patch
@@ -13,10 +13,10 @@ As part of: SportPaper (https://github.com/Electroid/SportPaper)
Licensed under: GPL-3.0-only (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index e36e18d0ad4efb648f43ba8dc78ddaca5ba2c0d5..03a84a55edebf6261b990274ae7681728d139f2a 100644
+index 80225959a9f599b05c0bf0d13455dba45a0c8983..fe7e351e30e6d371a7ffce89333e76e3bdb84a76 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1816,6 +1816,11 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1799,6 +1799,11 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@Override
public void destroyBlockProgress(final int id, final BlockPos blockPos, final int progress) {
@@ -28,7 +28,7 @@ index e36e18d0ad4efb648f43ba8dc78ddaca5ba2c0d5..03a84a55edebf6261b990274ae768172
// CraftBukkit start
Player breakerPlayer = null;
Entity entity = this.getEntity(id);
-@@ -1832,7 +1837,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1815,7 +1820,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
.callEvent();
}
// Paper end - Add BlockBreakProgressUpdateEvent
@@ -37,7 +37,7 @@ index e36e18d0ad4efb648f43ba8dc78ddaca5ba2c0d5..03a84a55edebf6261b990274ae768172
if (player.level() == this && player.getId() != id) {
double xd = blockPos.getX() - player.getX();
double yd = blockPos.getY() - player.getY();
-@@ -1843,7 +1848,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1826,7 +1831,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
continue;
}
// CraftBukkit end
diff --git a/leaf-server/minecraft-patches/features/0082-Pufferfish-Dynamic-Activation-of-Brain.patch b/leaf-server/minecraft-patches/features/0082-Pufferfish-Dynamic-Activation-of-Brain.patch
index 5332066a54..f5f4b42209 100644
--- a/leaf-server/minecraft-patches/features/0082-Pufferfish-Dynamic-Activation-of-Brain.patch
+++ b/leaf-server/minecraft-patches/features/0082-Pufferfish-Dynamic-Activation-of-Brain.patch
@@ -74,7 +74,7 @@ diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/
index fe7e351e30e6d371a7ffce89333e76e3bdb84a76..53f89b946ad7c4407918e362d0ca26208d877d79 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -920,6 +920,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -903,6 +903,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
this.entityTickList
.forEach(
entity -> {
diff --git a/leaf-server/minecraft-patches/features/0100-Leaves-Replay-Mod-API.patch b/leaf-server/minecraft-patches/features/0100-Leaves-Replay-Mod-API.patch
index 9aaf75b4ad..cf96a80a96 100644
--- a/leaf-server/minecraft-patches/features/0100-Leaves-Replay-Mod-API.patch
+++ b/leaf-server/minecraft-patches/features/0100-Leaves-Replay-Mod-API.patch
@@ -299,7 +299,7 @@ index 37c9f983ae89c497f44a1b7967d741abf909e0a0..d7fb2505f2a86ca0d81f144624ef2e1a
this.setListData(players);
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 2f4c5b2653d798bf6adbe5b8e8b190ae78fd9f82..9dbcf633e41a991923d28cb6f9ce925667fffee8 100644
+index 53f89b946ad7c4407918e362d0ca26208d877d79..6638155dd34519592893bf992ad5050f30c39626 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -238,6 +238,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -310,7 +310,7 @@ index 2f4c5b2653d798bf6adbe5b8e8b190ae78fd9f82..9dbcf633e41a991923d28cb6f9ce9256
@Override
public @Nullable LevelChunk getChunkIfLoaded(int x, int z) {
-@@ -777,6 +778,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -760,6 +761,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
this.chunkDataController = new ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller.ChunkDataController((ServerLevel)(Object)this, this.chunkTaskScheduler);
// Paper end - rewrite chunk system
this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit
@@ -318,7 +318,7 @@ index 2f4c5b2653d798bf6adbe5b8e8b190ae78fd9f82..9dbcf633e41a991923d28cb6f9ce9256
}
// Paper start
-@@ -2962,6 +2964,11 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -2945,6 +2947,11 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
// ServerLevel.this.getChunkSource().addEntity(entity); // Paper - ignore and warn about illegal addEntity calls instead of crashing server; moved down below valid=true
if (entity instanceof ServerPlayer player) {
ServerLevel.this.players.add(player);
@@ -330,7 +330,7 @@ index 2f4c5b2653d798bf6adbe5b8e8b190ae78fd9f82..9dbcf633e41a991923d28cb6f9ce9256
if (player.isReceivingWaypoints()) {
ServerLevel.this.getWaypointManager().addPlayer(player);
}
-@@ -3040,6 +3047,11 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -3023,6 +3030,11 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
ServerLevel.this.getChunkSource().removeEntity(entity);
if (entity instanceof ServerPlayer player) {
ServerLevel.this.players.remove(player);
diff --git a/leaf-server/minecraft-patches/features/0103-Reduce-canSee-work.patch b/leaf-server/minecraft-patches/features/0103-Reduce-canSee-work.patch
index d054eea247..319ab67be5 100644
--- a/leaf-server/minecraft-patches/features/0103-Reduce-canSee-work.patch
+++ b/leaf-server/minecraft-patches/features/0103-Reduce-canSee-work.patch
@@ -7,10 +7,10 @@ Co-authored by: Martijn Muijsers
Co-authored by: MachineBreaker
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index d64c2837267958a1226b9216da587ca58770a600..3cb7f14b0f781dd93754f74ad3348d2bb5899c49 100644
+index 734a8fe92417aff18fb3964df2c90f46e054c86b..e5b33ef26c971348b03f176d9b860bff79f8bc61 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
-@@ -967,17 +967,19 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -957,17 +957,19 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
for (int i = 0, len = entities.size(); i < len; ++i) {
Entity entity = entities.get(i);
diff --git a/leaf-server/minecraft-patches/features/0119-Matter-Secure-Seed.patch b/leaf-server/minecraft-patches/features/0119-Matter-Secure-Seed.patch
index c3dc2ffe2f..ca63656bc9 100644
--- a/leaf-server/minecraft-patches/features/0119-Matter-Secure-Seed.patch
+++ b/leaf-server/minecraft-patches/features/0119-Matter-Secure-Seed.patch
@@ -45,10 +45,10 @@ index 98f07bacb2a94b368c1969dc0cfb11b72f6cea1a..6755281b1dedf2078e1623918985de77
}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 9328c7cf8b4c21e3405c9d86e2022dde6933cb8c..5be30f950fecce5e32b29e69dbb7faf3d54507a4 100644
+index 6638155dd34519592893bf992ad5050f30c39626..d8940a667cf9c8216dd67add38debc86c3e276c6 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -709,6 +709,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -692,6 +692,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
generator = new org.bukkit.craftbukkit.generator.CustomChunkGenerator(this, generator, gen);
}
// CraftBukkit end
diff --git a/leaf-server/minecraft-patches/features/0121-Faster-random-generator.patch b/leaf-server/minecraft-patches/features/0121-Faster-random-generator.patch
index 1d48c9a1c5..c63e5a9106 100644
--- a/leaf-server/minecraft-patches/features/0121-Faster-random-generator.patch
+++ b/leaf-server/minecraft-patches/features/0121-Faster-random-generator.patch
@@ -27,10 +27,10 @@ index 6755281b1dedf2078e1623918985de772bb78dfe..aa30556730b3c924f72ccd8702a88c38
final ServerLevel world = this.level;
final int randomTickSpeed = world.getGameRules().get(GameRules.RANDOM_TICK_SPEED);
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 5be30f950fecce5e32b29e69dbb7faf3d54507a4..729806478408d8f835520c7f50af97efee3d3597 100644
+index d8940a667cf9c8216dd67add38debc86c3e276c6..72b03879dee9613423648dd2763a76a28f375704 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1015,7 +1015,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -998,7 +998,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
private void optimiseRandomTick(final LevelChunk chunk, final int tickSpeed) {
final LevelChunkSection[] sections = chunk.getSections();
final int minSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection((ServerLevel)(Object)this);
@@ -39,7 +39,7 @@ index 5be30f950fecce5e32b29e69dbb7faf3d54507a4..729806478408d8f835520c7f50af97ef
final boolean doubleTickFluids = !ca.spottedleaf.moonrise.common.PlatformHooks.get().configFixMC224294();
final ChunkPos cpos = chunk.getPos();
-@@ -1062,7 +1062,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1045,7 +1045,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
// Paper end - optimise random ticking
public void tickChunk(final LevelChunk chunk, final int tickSpeed) {
@@ -97,7 +97,7 @@ index 664c1b3008b62c533b8e3302baa994ed0c135c42..bce2fc4fa3fc56edb2a29e5ba695ac8f
static RandomSource createThreadLocalInstance(final long seed) {
return new SingleThreadedRandomSource(seed);
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
-index 7035d1ede6383c2016685cd2ca2f86e269f75c73..50c424b1d498d052bd27f37e855d0db3f780ee0b 100644
+index c2b845232253f3930ef90e8970b745c3dfea9bbf..4845fc18afed8229a2a4f00de1269145ceacdfe0 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -173,7 +173,7 @@ public abstract class Entity
@@ -110,7 +110,7 @@ index 7035d1ede6383c2016685cd2ca2f86e269f75c73..50c424b1d498d052bd27f37e855d0db3
private static final class RandomRandomSource extends ca.spottedleaf.moonrise.common.util.ThreadUnsafeRandom {
public RandomRandomSource() {
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 3cb7f14b0f781dd93754f74ad3348d2bb5899c49..067d01e6f03ad75fdb3cc8163815247a84cfae0d 100644
+index e5b33ef26c971348b03f176d9b860bff79f8bc61..94619316b2f2cc919b66c334177239948e76ac75 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
@@ -132,7 +132,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
@@ -122,7 +122,7 @@ index 3cb7f14b0f781dd93754f74ad3348d2bb5899c49..067d01e6f03ad75fdb3cc8163815247a
@Deprecated
private final RandomSource soundSeedGenerator = RandomSource.createThreadSafe();
private final Holder dimensionTypeRegistration;
-@@ -190,7 +190,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -183,7 +183,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
public static @Nullable BlockPos lastPhysicsProblem; // Spigot
public final Map explosionDensityCache = new java.util.HashMap<>(); // Paper - Optimize explosions
public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Faster redstone torch rapid clock removal; Move from Map in BlockRedstoneTorch to here
diff --git a/leaf-server/minecraft-patches/features/0141-Remove-stream-in-CraftWorld-spawnParticle.patch b/leaf-server/minecraft-patches/features/0141-Remove-stream-in-CraftWorld-spawnParticle.patch
index 5e136fe085..39ea59ccd9 100644
--- a/leaf-server/minecraft-patches/features/0141-Remove-stream-in-CraftWorld-spawnParticle.patch
+++ b/leaf-server/minecraft-patches/features/0141-Remove-stream-in-CraftWorld-spawnParticle.patch
@@ -5,10 +5,10 @@ Subject: [PATCH] Remove stream in CraftWorld#spawnParticle
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index fd0f5e25b17a76fd1d4fcfa6017cd3dcf24a4520..71340b2d029a9e7ab7b4d2ce18698d4b32ba9f57 100644
+index 72b03879dee9613423648dd2763a76a28f375704..6cc37813c571cd5a4a7bb6e97de4956ed7c0e121 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -2273,7 +2273,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -2256,7 +2256,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
for (int i = 0; i < receivers.size(); i++) { // Paper - particle API
ServerPlayer player = receivers.get(i); // Paper - particle API
@@ -17,7 +17,7 @@ index fd0f5e25b17a76fd1d4fcfa6017cd3dcf24a4520..71340b2d029a9e7ab7b4d2ce18698d4b
if (this.sendParticles(player, overrideLimiter, x, y, z, packet)) {
result++;
}
-@@ -2282,6 +2282,44 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -2265,6 +2265,44 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
return result;
}
diff --git a/leaf-server/minecraft-patches/features/0174-Cache-chunk-key.patch b/leaf-server/minecraft-patches/features/0174-Cache-chunk-key.patch
index ed7b136ddd..ab83136854 100644
--- a/leaf-server/minecraft-patches/features/0174-Cache-chunk-key.patch
+++ b/leaf-server/minecraft-patches/features/0174-Cache-chunk-key.patch
@@ -119,7 +119,7 @@ index 63cc7970b9df5e84743084ca99650bc3d13b970e..022dd571623224fd87c4aa4a50ebdba4
// Paper end - rewrite chunk system
}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 71340b2d029a9e7ab7b4d2ce18698d4b32ba9f57..1054cb62eeba9be6d316d0e0d4067afadc331ba8 100644
+index 6cc37813c571cd5a4a7bb6e97de4956ed7c0e121..4bca5b3b368cda2229522c9ab2af3116b08f91a7 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -549,7 +549,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -131,7 +131,7 @@ index 71340b2d029a9e7ab7b4d2ce18698d4b32ba9f57..1054cb62eeba9be6d316d0e0d4067afa
return;
}
-@@ -2840,7 +2840,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -2823,7 +2823,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
public boolean areEntitiesActuallyLoadedAndTicking(final ChunkPos pos) {
// Paper start - rewrite chunk system
@@ -140,7 +140,7 @@ index 71340b2d029a9e7ab7b4d2ce18698d4b32ba9f57..1054cb62eeba9be6d316d0e0d4067afa
return chunkHolder != null && chunkHolder.isEntityTickingReady();
// Paper end - rewrite chunk system
}
-@@ -2860,7 +2860,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -2843,7 +2843,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
public boolean canSpawnEntitiesInChunk(final ChunkPos pos) {
// Paper start - rewrite chunk system
diff --git a/leaf-server/minecraft-patches/features/0176-Paper-PR-Fix-MC-117075-Block-Entities-Unload-Lag-Spi.patch b/leaf-server/minecraft-patches/features/0176-Paper-PR-Fix-MC-117075-Block-Entities-Unload-Lag-Spi.patch
index 4be12c2366..a87d85b785 100644
--- a/leaf-server/minecraft-patches/features/0176-Paper-PR-Fix-MC-117075-Block-Entities-Unload-Lag-Spi.patch
+++ b/leaf-server/minecraft-patches/features/0176-Paper-PR-Fix-MC-117075-Block-Entities-Unload-Lag-Spi.patch
@@ -12,7 +12,7 @@ We replaced the `blockEntityTickers` list with a custom list based on fastutil's
This is WAY FASTER than using `removeAll` with a list of entries to be removed, because we don't need to calculate the identity of each block entity to be removed, and we can jump directly to where the search should begin, giving a performance boost for small removals (because we don't need to loop thru the entire list to find what element should be removed) and a performance boost for big removals (no need to calculate the identity of each block entity).
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 067d01e6f03ad75fdb3cc8163815247a84cfae0d..dbabd052dc01c8a5f12f3df95b40b7176d7a6a0a 100644
+index 94619316b2f2cc919b66c334177239948e76ac75..786117f3fc54933454bf4916b19e2128e7581427 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
@@ -119,7 +119,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
@@ -24,7 +24,7 @@ index 067d01e6f03ad75fdb3cc8163815247a84cfae0d..dbabd052dc01c8a5f12f3df95b40b717
protected final CollectingNeighborUpdater neighborUpdater;
private final List pendingBlockEntityTickers = Lists.newArrayList();
private boolean tickingBlockEntities;
-@@ -1565,13 +1565,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1555,13 +1555,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
int tickedEntities = 0; // Paper - rewrite chunk system
// Paper start - Fix MC-117075 use removeAll
@@ -39,7 +39,7 @@ index 067d01e6f03ad75fdb3cc8163815247a84cfae0d..dbabd052dc01c8a5f12f3df95b40b717
} else if (tickBlockEntities && this.shouldTickBlocksAt(ticker.getPos())) {
ticker.tick();
// Paper start - rewrite chunk system
-@@ -1582,7 +1580,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1572,7 +1570,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
}
}
diff --git a/leaf-server/minecraft-patches/features/0184-optimize-mob-despawn.patch b/leaf-server/minecraft-patches/features/0184-optimize-mob-despawn.patch
index 385477fd27..eaa7e670b2 100644
--- a/leaf-server/minecraft-patches/features/0184-optimize-mob-despawn.patch
+++ b/leaf-server/minecraft-patches/features/0184-optimize-mob-despawn.patch
@@ -5,10 +5,10 @@ Subject: [PATCH] optimize mob despawn
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 6c5e50d7e66840a659264e69b3b7a9213f05a384..e6ddeb1849cfb5173491d339cbb54d7b6a29dc07 100644
+index 4bca5b3b368cda2229522c9ab2af3116b08f91a7..fdba2c55ca108a3ff467e4cc84320f451f4151f4 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -920,6 +920,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -903,6 +903,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
}
io.papermc.paper.entity.activation.ActivationRange.activateEntities(this); // Paper - EAR
@@ -16,7 +16,7 @@ index 6c5e50d7e66840a659264e69b3b7a9213f05a384..e6ddeb1849cfb5173491d339cbb54d7b
this.entityTickList
.forEach(
entity -> {
-@@ -927,7 +928,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -910,7 +911,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
if (!entity.isRemoved()) {
if (!tickRateManager.isEntityFrozen(entity)) {
profiler.push("checkDespawn");
@@ -25,7 +25,7 @@ index 6c5e50d7e66840a659264e69b3b7a9213f05a384..e6ddeb1849cfb5173491d339cbb54d7b
profiler.pop();
if (true) { // Paper - rewrite chunk system
Entity vehicle = entity.getVehicle();
-@@ -1061,6 +1062,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1044,6 +1045,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
}
// Paper end - optimise random ticking
diff --git a/leaf-server/minecraft-patches/features/0192-SparklyPaper-Parallel-world-ticking.patch b/leaf-server/minecraft-patches/features/0192-SparklyPaper-Parallel-world-ticking.patch
index 65f1336f7a..21fdbdc41a 100644
--- a/leaf-server/minecraft-patches/features/0192-SparklyPaper-Parallel-world-ticking.patch
+++ b/leaf-server/minecraft-patches/features/0192-SparklyPaper-Parallel-world-ticking.patch
@@ -365,7 +365,7 @@ index aa30556730b3c924f72ccd8702a88c380d42c6a9..d9d0a7693e86c244af47513ae1a3e39e
continue;
}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 9f34b604d5164c962f1a289d90ca1bf8ce2c1cec..6b1608ce153bb7b06da301f6372cf152f9f579a8 100644
+index fdba2c55ca108a3ff467e4cc84320f451f4151f4..d3d962689505b906767348898c9461bcd50829d3 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -196,7 +196,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -389,7 +389,7 @@ index 9f34b604d5164c962f1a289d90ca1bf8ce2c1cec..6b1608ce153bb7b06da301f6372cf152
// CraftBukkit start
private final ResourceKey typeKey;
-@@ -780,6 +785,15 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -763,6 +768,15 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
// Paper end - rewrite chunk system
this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit
this.realPlayers = Lists.newArrayList(); // Leaves - skip
@@ -405,7 +405,7 @@ index 9f34b604d5164c962f1a289d90ca1bf8ce2c1cec..6b1608ce153bb7b06da301f6372cf152
}
// Paper start
-@@ -833,10 +847,147 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -816,10 +830,147 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
return previous;
}
@@ -553,7 +553,7 @@ index 9f34b604d5164c962f1a289d90ca1bf8ce2c1cec..6b1608ce153bb7b06da301f6372cf152
TickRateManager tickRateManager = this.tickRateManager();
boolean runs = tickRateManager.runsNormally();
if (runs) {
-@@ -847,6 +998,12 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -830,6 +981,12 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
profiler.pop();
}
@@ -566,7 +566,7 @@ index 9f34b604d5164c962f1a289d90ca1bf8ce2c1cec..6b1608ce153bb7b06da301f6372cf152
int percentage = this.getGameRules().get(GameRules.PLAYERS_SLEEPING_PERCENTAGE);
if (this.purpurConfig.playersSkipNight && this.sleepStatus.areEnoughSleeping(percentage) && this.sleepStatus.areEnoughDeepSleeping(percentage, this.players)) { // Purpur - Config for skipping night
Optional> defaultClock = this.dimensionType().defaultClock();
-@@ -952,6 +1109,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -935,6 +1092,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD);
// Paper end - Prevent block entity and entity crashes
}
@@ -574,7 +574,7 @@ index 9f34b604d5164c962f1a289d90ca1bf8ce2c1cec..6b1608ce153bb7b06da301f6372cf152
this.moonrise$midTickTasks(); // Paper - rewrite chunk system
// Gale end - Airplane - remove lambda from ticking guard - copied from guardEntityTick
profiler.pop();
-@@ -1459,7 +1617,10 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1442,7 +1600,10 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
fluidState.tick(this, pos, blockState);
}
// Paper start - rewrite chunk system
@@ -586,7 +586,7 @@ index 9f34b604d5164c962f1a289d90ca1bf8ce2c1cec..6b1608ce153bb7b06da301f6372cf152
((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks();
}
// Paper end - rewrite chunk system
-@@ -1472,7 +1633,10 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1455,7 +1616,10 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
state.tick(this, pos, this.random);
}
// Paper start - rewrite chunk system
@@ -598,7 +598,7 @@ index 9f34b604d5164c962f1a289d90ca1bf8ce2c1cec..6b1608ce153bb7b06da301f6372cf152
((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks();
}
// Paper end - rewrite chunk system
-@@ -1724,6 +1888,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1707,6 +1871,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
}
private void addPlayer(final ServerPlayer player) {
@@ -606,7 +606,7 @@ index 9f34b604d5164c962f1a289d90ca1bf8ce2c1cec..6b1608ce153bb7b06da301f6372cf152
Entity existing = this.getEntity(player.getUUID());
if (existing != null) {
LOGGER.warn("Force-added player with duplicate UUID {}", player.getUUID());
-@@ -1736,7 +1901,13 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1719,7 +1884,13 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
// CraftBukkit start
private boolean addEntity(final Entity entity, final org.bukkit.event.entity.CreatureSpawnEvent.@Nullable SpawnReason spawnReason) {
@@ -1098,18 +1098,18 @@ index 6e5438a327287981d7732659530fba6cd06b591e..8b1582e2f881c581bd6788c587b830ef
DataComponentPatch newPatch = this.components.asPatch();
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index dbabd052dc01c8a5f12f3df95b40b7176d7a6a0a..2c49a363d3d457a360e05d7f546eb63e09805dd7 100644
+index 786117f3fc54933454bf4916b19e2128e7581427..6f360efdd6248cef1cf3d1392eeae4d273c80a79 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
-@@ -187,6 +187,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
- // Leaf end - per-world configuration
+@@ -180,6 +180,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+ // Gale end - Gale configuration
public final org.purpurmc.purpur.PurpurWorldConfig purpurConfig; // Purpur - Purpur config files
+ public final io.papermc.paper.redstone.RedstoneWireTurbo turbo; // Leaf - SparklyPaper - parallel world ticking - moved to world
public static @Nullable BlockPos lastPhysicsProblem; // Spigot
public final Map explosionDensityCache = new java.util.HashMap<>(); // Paper - Optimize explosions
public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Faster redstone torch rapid clock removal; Move from Map in BlockRedstoneTorch to here
-@@ -942,6 +943,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -932,6 +933,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
this.damageSources = new DamageSources(registryAccess);
this.entityLookup = new ca.spottedleaf.moonrise.patches.chunk_system.level.entity.dfl.DefaultEntityLookup(this); // Paper - rewrite chunk system
this.chunkPacketBlockController = this.paperConfig().anticheat.antiXray.enabled ? new io.papermc.paper.antixray.ChunkPacketBlockControllerAntiXray(this, executor) : io.papermc.paper.antixray.ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray
@@ -1117,7 +1117,7 @@ index dbabd052dc01c8a5f12f3df95b40b7176d7a6a0a..2c49a363d3d457a360e05d7f546eb63e
}
public int getNextEntityId() {
-@@ -1123,6 +1125,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1113,6 +1115,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
@Override
public boolean setBlock(final BlockPos pos, final BlockState blockState, final @Block.UpdateFlags int updateFlags, final int updateLimit) {
@@ -1125,7 +1125,7 @@ index dbabd052dc01c8a5f12f3df95b40b7176d7a6a0a..2c49a363d3d457a360e05d7f546eb63e
// CraftBukkit start - tree generation
if (this.captureTreeGeneration) {
// Paper start - Protect Bedrock and End Portal/Frames from being destroyed
-@@ -1573,7 +1576,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1563,7 +1566,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
} else if (tickBlockEntities && this.shouldTickBlocksAt(ticker.getPos())) {
ticker.tick();
// Paper start - rewrite chunk system
@@ -1137,7 +1137,7 @@ index dbabd052dc01c8a5f12f3df95b40b7176d7a6a0a..2c49a363d3d457a360e05d7f546eb63e
((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)(Level)(Object)this).moonrise$midTickTasks();
}
// Paper end - rewrite chunk system
-@@ -1595,6 +1601,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1585,6 +1591,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); // Gale - Airplane - remove lambda from ticking guard - diff on change ServerLevel#tick
// Paper end - Prevent block entity and entity crashes
}
@@ -1145,7 +1145,7 @@ index dbabd052dc01c8a5f12f3df95b40b7176d7a6a0a..2c49a363d3d457a360e05d7f546eb63e
this.moonrise$midTickTasks(); // Paper - rewrite chunk system // Gale - Airplane - remove lambda from ticking guard - diff on change ServerLevel#tick
}
-@@ -1746,6 +1753,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1736,6 +1743,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
@Override
public @Nullable BlockEntity getBlockEntity(final BlockPos pos) {
@@ -1153,7 +1153,7 @@ index dbabd052dc01c8a5f12f3df95b40b7176d7a6a0a..2c49a363d3d457a360e05d7f546eb63e
// Paper start - Perf: Optimize capturedTileEntities lookup
net.minecraft.world.level.block.entity.BlockEntity blockEntity;
if (!this.capturedBlockEntities.isEmpty() && (blockEntity = this.capturedBlockEntities.get(pos)) != null) {
-@@ -1762,6 +1770,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1752,6 +1760,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
}
public void setBlockEntity(final BlockEntity blockEntity) {
@@ -1161,7 +1161,7 @@ index dbabd052dc01c8a5f12f3df95b40b7176d7a6a0a..2c49a363d3d457a360e05d7f546eb63e
BlockPos pos = blockEntity.getBlockPos();
if (this.isInValidBounds(pos)) {
// CraftBukkit start
-@@ -1834,6 +1843,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1824,6 +1833,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
@Override
public List getEntities(final @Nullable Entity except, final AABB bb, final Predicate super Entity> selector) {
diff --git a/leaf-server/minecraft-patches/features/0194-Paper-PR-Fix-cancelled-Projectile-Events-still-consu.patch b/leaf-server/minecraft-patches/features/0194-Paper-PR-Fix-cancelled-Projectile-Events-still-consu.patch
index e3e6056dbf..6941ba1b11 100644
--- a/leaf-server/minecraft-patches/features/0194-Paper-PR-Fix-cancelled-Projectile-Events-still-consu.patch
+++ b/leaf-server/minecraft-patches/features/0194-Paper-PR-Fix-cancelled-Projectile-Events-still-consu.patch
@@ -244,10 +244,10 @@ index 039fc3ced4e06a97346264b52375ddd298fb8422..e6e88258a67ecc5a098ad903221fdc0f
player.getInventory().removeItem(projectile);
}
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 2c49a363d3d457a360e05d7f546eb63e09805dd7..8f934577eed411bdca128e5bd8de86d86be7238c 100644
+index 6f360efdd6248cef1cf3d1392eeae4d273c80a79..909b22783a482c9d96f088f288305323e8487149 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
-@@ -192,6 +192,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -185,6 +185,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
public final Map explosionDensityCache = new java.util.HashMap<>(); // Paper - Optimize explosions
public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Faster redstone torch rapid clock removal; Move from Map in BlockRedstoneTorch to here
public final net.minecraft.world.level.levelgen.BitRandomSource simpleRandom = org.dreeam.leaf.config.modules.opt.FastRNG.enabled ? new org.dreeam.leaf.util.math.random.FasterRandomSource(net.minecraft.world.level.levelgen.RandomSupport.generateUniqueSeed()) : new ca.spottedleaf.moonrise.common.util.SimpleThreadUnsafeRandom(net.minecraft.world.level.levelgen.RandomSupport.generateUniqueSeed()); // Gale - Pufferfish - move random tick random // Leaf - Faster random generator
diff --git a/leaf-server/minecraft-patches/features/0197-Use-BFS-on-getSlopeDistance.patch b/leaf-server/minecraft-patches/features/0197-Use-BFS-on-getSlopeDistance.patch
index 66a6716781..76ed026eaa 100644
--- a/leaf-server/minecraft-patches/features/0197-Use-BFS-on-getSlopeDistance.patch
+++ b/leaf-server/minecraft-patches/features/0197-Use-BFS-on-getSlopeDistance.patch
@@ -9,10 +9,10 @@ Leaf: ~48ms (-36%)
This should help drastically on the farms that use actively changing fluids.
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 2fde45ce9c132cf385d1963202e163d4efb0a4b9..111f9f97c8792f30be283eff3db0823c4edde483 100644
+index e7815ca0ef0a03d70850a23de591615675df8ece..cd7883d18511c2f5ce73f41afd676da0ee2d86ff 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1616,6 +1616,10 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1599,6 +1599,10 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
this.emptyTime = 0;
}
diff --git a/leaf-server/minecraft-patches/features/0200-Raytrace-AntiXray-SDK-integration.patch b/leaf-server/minecraft-patches/features/0200-Raytrace-AntiXray-SDK-integration.patch
index c4aecb5612..6e79a6020f 100644
--- a/leaf-server/minecraft-patches/features/0200-Raytrace-AntiXray-SDK-integration.patch
+++ b/leaf-server/minecraft-patches/features/0200-Raytrace-AntiXray-SDK-integration.patch
@@ -25,10 +25,10 @@ index 185a14c96741d9c394c07b1c7747e908282e9452..df23b1b27028ccb948252f0cd51244ed
}
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 8f934577eed411bdca128e5bd8de86d86be7238c..9e99842ed56be3cad364c84f50eb55fc9c17c3da 100644
+index 909b22783a482c9d96f088f288305323e8487149..a78e0e8f7c0be8727368efa4bd801fcc2b6c5735 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
-@@ -1168,6 +1168,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1158,6 +1158,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
}
// CraftBukkit end - capture blockstates
BlockState oldState = chunk.setBlockState(pos, blockState, updateFlags);
diff --git a/leaf-server/minecraft-patches/features/0227-optimize-mob-spawning.patch b/leaf-server/minecraft-patches/features/0227-optimize-mob-spawning.patch
index aaff6e38a2..46be162145 100644
--- a/leaf-server/minecraft-patches/features/0227-optimize-mob-spawning.patch
+++ b/leaf-server/minecraft-patches/features/0227-optimize-mob-spawning.patch
@@ -16,7 +16,7 @@ Generally faster than the non-async approach
iterate over all entities, get their chunk, and increment the count
diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java
-index 22c3d2350be9c82a5e59558fdfd09c43ac874982..b110b81ed9bca5c4ee15a77bbc6887746562caba 100644
+index 7bc1636e8ae526e7ffbb8b34b3b777b6f407f0b4..13c5ebb35cfe06636bcd0bcd06fc3ddbbfa0d711 100644
--- a/net/minecraft/server/level/ChunkMap.java
+++ b/net/minecraft/server/level/ChunkMap.java
@@ -275,6 +275,7 @@ public class ChunkMap extends SimpleRegionStorage implements ChunkHolder.PlayerP
@@ -191,10 +191,10 @@ index d9d0a7693e86c244af47513ae1a3e39e2bb63a31..b80b408d8b087e37b06e9ad4c096e0b6
}
}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index affe5d48a415929db2ad25c3bdaa7df706cf068d..a758fab1b83b024370070b30f5ec76620a72677b 100644
+index cd7883d18511c2f5ce73f41afd676da0ee2d86ff..8bfa430a066da88af2af0bc470768cc338072659 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1227,6 +1227,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1210,6 +1210,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
// Paper end - optimise random ticking
private final org.dreeam.leaf.world.DespawnMap despawnMap = new org.dreeam.leaf.world.DespawnMap(); // Leaf - optimize despawn
diff --git a/leaf-server/minecraft-patches/features/0234-optimize-random-tick.patch b/leaf-server/minecraft-patches/features/0234-optimize-random-tick.patch
index 42663175ce..c22ede46ee 100644
--- a/leaf-server/minecraft-patches/features/0234-optimize-random-tick.patch
+++ b/leaf-server/minecraft-patches/features/0234-optimize-random-tick.patch
@@ -24,10 +24,10 @@ index b80b408d8b087e37b06e9ad4c096e0b6cb7c0a86..78ec485515b53a4d61d577b4a688f127
profiler.popPush("customSpawners");
this.level.tickCustomSpawners(this.spawnEnemies);
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index a758fab1b83b024370070b30f5ec76620a72677b..4c2372a4404d110bf7d73a0060dedb8381c5e6a2 100644
+index 8bfa430a066da88af2af0bc470768cc338072659..a127bf1fcdc71f821685099646a4846c08b7581a 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1228,6 +1228,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1211,6 +1211,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
private final org.dreeam.leaf.world.DespawnMap despawnMap = new org.dreeam.leaf.world.DespawnMap(); // Leaf - optimize despawn
public final org.dreeam.leaf.world.NatureSpawnChunkMap natureSpawnChunkMap = new org.dreeam.leaf.world.NatureSpawnChunkMap(); // Leaf - optimize mob spawning
diff --git a/leaf-server/minecraft-patches/features/0239-Paw-optimization.patch b/leaf-server/minecraft-patches/features/0239-Paw-optimization.patch
index 3e04f36dd3..0cd4a537f3 100644
--- a/leaf-server/minecraft-patches/features/0239-Paw-optimization.patch
+++ b/leaf-server/minecraft-patches/features/0239-Paw-optimization.patch
@@ -81,10 +81,10 @@ index 78ec485515b53a4d61d577b4a688f12722655cf9..b26d7cbe440abf35952c1bab3a2487cf
profiler.popPush("tickSpawningChunks");
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index a006237382c0686f7916113b0a2ebd89ce4e6b78..f3dcaa2f126eaf138ad4e40760fc5499e33267a5 100644
+index a127bf1fcdc71f821685099646a4846c08b7581a..acc05a34b5ade35e4fbba1c9bc5987c081b843b3 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1655,26 +1655,8 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1638,26 +1638,8 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
}
@@ -111,7 +111,7 @@ index a006237382c0686f7916113b0a2ebd89ce4e6b78..f3dcaa2f126eaf138ad4e40760fc5499
entity.setOldPosAndRot();
ProfilerFiller profiler = Profiler.get();
entity.tickCount++;
-@@ -1691,13 +1673,6 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1674,13 +1656,6 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
for (Entity passenger : entity.getPassengers()) {
this.tickPassenger(entity, passenger, isActive); // Paper - EAR 2
}
diff --git a/leaf-server/minecraft-patches/features/0248-Paper-PR-Optimise-temptation-lookups.patch b/leaf-server/minecraft-patches/features/0248-Paper-PR-Optimise-temptation-lookups.patch
index f24328d216..1ec28f2f58 100644
--- a/leaf-server/minecraft-patches/features/0248-Paper-PR-Optimise-temptation-lookups.patch
+++ b/leaf-server/minecraft-patches/features/0248-Paper-PR-Optimise-temptation-lookups.patch
@@ -113,10 +113,10 @@ index 0000000000000000000000000000000000000000..c3339b22929cb4e3b216aadf1069daa1
+ }
+}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index f3dcaa2f126eaf138ad4e40760fc5499e33267a5..85db827959ccc1f7a186dce08976ecb44511b8d6 100644
+index acc05a34b5ade35e4fbba1c9bc5987c081b843b3..29bc5c4bbec66562888758157685645f5922841c 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1084,6 +1084,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1067,6 +1067,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
io.papermc.paper.entity.activation.ActivationRange.activateEntities(this); // Paper - EAR
boolean didDespawn = tickRateManager.runsNormally() && despawnMap.tick(this, this.entityTickList); // Leaf - optimize despawn
@@ -124,7 +124,7 @@ index f3dcaa2f126eaf138ad4e40760fc5499e33267a5..85db827959ccc1f7a186dce08976ecb4
this.entityTickList
.forEach(
entity -> {
-@@ -3310,4 +3311,11 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -3293,4 +3294,11 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
this.lagCompensationTick = (System.nanoTime() - MinecraftServer.SERVER_INIT) / (java.util.concurrent.TimeUnit.MILLISECONDS.toNanos(50L));
}
// Paper end - lag compensation
diff --git a/leaf-server/minecraft-patches/features/0249-Paper-PR-Optimise-temptation-lookups-changes.patch b/leaf-server/minecraft-patches/features/0249-Paper-PR-Optimise-temptation-lookups-changes.patch
index 8483ba0db4..92af6c913c 100644
--- a/leaf-server/minecraft-patches/features/0249-Paper-PR-Optimise-temptation-lookups-changes.patch
+++ b/leaf-server/minecraft-patches/features/0249-Paper-PR-Optimise-temptation-lookups-changes.patch
@@ -98,7 +98,7 @@ diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/
index 29bc5c4bbec66562888758157685645f5922841c..ea1af07226c9b0d20f0edb5b97786d42a4c4eece 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1084,7 +1084,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1067,7 +1067,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
io.papermc.paper.entity.activation.ActivationRange.activateEntities(this); // Paper - EAR
boolean didDespawn = tickRateManager.runsNormally() && despawnMap.tick(this, this.entityTickList); // Leaf - optimize despawn
diff --git a/leaf-server/minecraft-patches/features/0255-thread-unsafe-chunk-map.patch b/leaf-server/minecraft-patches/features/0255-thread-unsafe-chunk-map.patch
index 79829cacbf..63790345f3 100644
--- a/leaf-server/minecraft-patches/features/0255-thread-unsafe-chunk-map.patch
+++ b/leaf-server/minecraft-patches/features/0255-thread-unsafe-chunk-map.patch
@@ -190,10 +190,10 @@ index b26d7cbe440abf35952c1bab3a2487cf0be7d6e4..b3de0c91e4e53b8b554128ec86656597
return ret;
}
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 9e99842ed56be3cad364c84f50eb55fc9c17c3da..00eddc54d7b1fc83b648df1fb2f5eb05b7e22521 100644
+index a78e0e8f7c0be8727368efa4bd801fcc2b6c5735..7b5b35c206798a0ac904f7a7389a0468018dd11c 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
-@@ -1052,6 +1052,16 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1042,6 +1042,16 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
// Paper end - Perf: make sure loaded chunks get the inlined variant of this function
}
diff --git a/leaf-server/minecraft-patches/features/0257-optimize-get-chunk.patch b/leaf-server/minecraft-patches/features/0257-optimize-get-chunk.patch
index d3be5a6b65..10910694c8 100644
--- a/leaf-server/minecraft-patches/features/0257-optimize-get-chunk.patch
+++ b/leaf-server/minecraft-patches/features/0257-optimize-get-chunk.patch
@@ -5,10 +5,10 @@ Subject: [PATCH] optimize get chunk
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 00eddc54d7b1fc83b648df1fb2f5eb05b7e22521..6dc9c01356a991cebf2e1102d2c2f8cb5f0fa23e 100644
+index 7b5b35c206798a0ac904f7a7389a0468018dd11c..a1d43377cbc6a37fe3199ecf886b511c879b9904 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
-@@ -1405,12 +1405,17 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1395,12 +1395,17 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
}
}
// CraftBukkit end
diff --git a/leaf-server/minecraft-patches/features/0258-remove-shouldTickBlocksAt-check.patch b/leaf-server/minecraft-patches/features/0258-remove-shouldTickBlocksAt-check.patch
index 19f35d30ea..79507f1028 100644
--- a/leaf-server/minecraft-patches/features/0258-remove-shouldTickBlocksAt-check.patch
+++ b/leaf-server/minecraft-patches/features/0258-remove-shouldTickBlocksAt-check.patch
@@ -5,10 +5,10 @@ Subject: [PATCH] remove shouldTickBlocksAt check
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 6dc9c01356a991cebf2e1102d2c2f8cb5f0fa23e..69e9861df42d2f03c2301cad1c28225e17df9926 100644
+index a1d43377cbc6a37fe3199ecf886b511c879b9904..c748af5c1b443bcb4863dd14cc206f36bfef640b 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
-@@ -1595,7 +1595,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1585,7 +1585,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
// Paper end - Fix MC-117075 use removeAll
if (ticker.isRemoved()) {
((org.dreeam.leaf.util.list.BlockEntityTickersList) this.blockEntityTickers).markAsRemoved(tickerIndex); // Paper - Fix MC-117075; use removeAll // SparklyPaper - optimize block entity removals
diff --git a/leaf-server/minecraft-patches/features/0262-optimize-fluid-state-access.patch b/leaf-server/minecraft-patches/features/0262-optimize-fluid-state-access.patch
index 84500e19b8..f3dff87baf 100644
--- a/leaf-server/minecraft-patches/features/0262-optimize-fluid-state-access.patch
+++ b/leaf-server/minecraft-patches/features/0262-optimize-fluid-state-access.patch
@@ -5,10 +5,10 @@ Subject: [PATCH] optimize fluid state access
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 69e9861df42d2f03c2301cad1c28225e17df9926..6f730baf229b768d8845da3b526890adebe6d682 100644
+index c748af5c1b443bcb4863dd14cc206f36bfef640b..8658c54d489a0f9561b19dd371346c0acb7c9fd5 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
-@@ -1428,6 +1428,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1418,6 +1418,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
return chunk.getFluidState(pos);
}
diff --git a/leaf-server/minecraft-patches/features/0267-cache-collision-list.patch b/leaf-server/minecraft-patches/features/0267-cache-collision-list.patch
index e7e960a8a1..580b9a068e 100644
--- a/leaf-server/minecraft-patches/features/0267-cache-collision-list.patch
+++ b/leaf-server/minecraft-patches/features/0267-cache-collision-list.patch
@@ -8,7 +8,7 @@ diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/
index ea1af07226c9b0d20f0edb5b97786d42a4c4eece..146cd6743bca87c377d170567a27bb26fbd3e20b 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1230,6 +1230,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1213,6 +1213,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
private final org.dreeam.leaf.world.DespawnMap despawnMap = new org.dreeam.leaf.world.DespawnMap(); // Leaf - optimize despawn
public final org.dreeam.leaf.world.NatureSpawnChunkMap natureSpawnChunkMap = new org.dreeam.leaf.world.NatureSpawnChunkMap(); // Leaf - optimize mob spawning
public final org.dreeam.leaf.world.RandomTickSystem randomTickSystem = new org.dreeam.leaf.world.RandomTickSystem(); // Leaf - optimize random tick
diff --git a/leaf-server/minecraft-patches/features/0268-fast-bit-radix-sort.patch b/leaf-server/minecraft-patches/features/0268-fast-bit-radix-sort.patch
index 593fd7f3c5..07cf4ca378 100644
--- a/leaf-server/minecraft-patches/features/0268-fast-bit-radix-sort.patch
+++ b/leaf-server/minecraft-patches/features/0268-fast-bit-radix-sort.patch
@@ -6,10 +6,10 @@ Subject: [PATCH] fast bit radix sort
Co-authored-by: Taiyou06
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 34218952dba0869126c75990495a4bc85ef3b76a..c1baa95d4ea660c4ebc16c662532618cf64aa58f 100644
+index 146cd6743bca87c377d170567a27bb26fbd3e20b..4b6fa6ef73d42324fec1cfe06b8de880abd8a911 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1231,6 +1231,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1214,6 +1214,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
public final org.dreeam.leaf.world.NatureSpawnChunkMap natureSpawnChunkMap = new org.dreeam.leaf.world.NatureSpawnChunkMap(); // Leaf - optimize mob spawning
public final org.dreeam.leaf.world.RandomTickSystem randomTickSystem = new org.dreeam.leaf.world.RandomTickSystem(); // Leaf - optimize random tick
public final org.dreeam.leaf.world.EntityCollisionCache entityCollisionCache = new org.dreeam.leaf.world.EntityCollisionCache(); // Leaf - cache collision list
diff --git a/leaf-server/minecraft-patches/features/0270-Pluto-Expose-Direction-Plane-s-faces.patch b/leaf-server/minecraft-patches/features/0270-Pluto-Expose-Direction-Plane-s-faces.patch
index 00d20fb253..ba4424eb0d 100644
--- a/leaf-server/minecraft-patches/features/0270-Pluto-Expose-Direction-Plane-s-faces.patch
+++ b/leaf-server/minecraft-patches/features/0270-Pluto-Expose-Direction-Plane-s-faces.patch
@@ -35,10 +35,10 @@ index 9548fea3c629db7829e4b83a18a7749d1f15f6a4..09e727f03fa6731b3325478c43aed3cb
}
}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index e49db5db6aa732a81f1022f66c5eb1663d924bfa..253a2d3b640b330aa2d12a108061bf717daf48fd 100644
+index 4b6fa6ef73d42324fec1cfe06b8de880abd8a911..58724d8f65898f7f532621fbae2cff4eb1796f80 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1322,7 +1322,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1305,7 +1305,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
// We only need to check blocks that are taller than the minimum step height
if (org.purpurmc.purpur.PurpurConfig.smoothSnowAccumulationStep > 0 && currentLayers >= org.purpurmc.purpur.PurpurConfig.smoothSnowAccumulationStep) {
int layersValueMin = currentLayers - org.purpurmc.purpur.PurpurConfig.smoothSnowAccumulationStep;
@@ -100,10 +100,10 @@ index 187cca14c69b2b472770d949cf61e4c0b7e098c6..668316a966bed87f2ef4aa8a9c5bf194
}
}
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 6f730baf229b768d8845da3b526890adebe6d682..7224f2a8968745bb7231cf78a1d6f2da53656a01 100644
+index 8658c54d489a0f9561b19dd371346c0acb7c9fd5..90009387d495db3fb3f68cd7d1ebe469b9aa1c9a 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
-@@ -2160,7 +2160,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -2150,7 +2150,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
public abstract Scoreboard getScoreboard();
public void updateNeighbourForOutputSignal(final BlockPos pos, final Block changedBlock) {
diff --git a/leaf-server/minecraft-patches/features/0275-Multithreaded-Tracker.patch b/leaf-server/minecraft-patches/features/0275-Multithreaded-Tracker.patch
index c3010fcfdf..c966eab1ce 100644
--- a/leaf-server/minecraft-patches/features/0275-Multithreaded-Tracker.patch
+++ b/leaf-server/minecraft-patches/features/0275-Multithreaded-Tracker.patch
@@ -1069,7 +1069,7 @@ index 58724d8f65898f7f532621fbae2cff4eb1796f80..231925545b6543004a0f6164bf73ee71
@Override
public @Nullable LevelChunk getChunkIfLoaded(int x, int z) {
-@@ -1146,6 +1147,8 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1129,6 +1130,8 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
this.debugSynchronizers.tick(this.server.debugSubscribers());
profiler.pop();
diff --git a/leaf-server/minecraft-patches/features/0277-Rewrite-entity-despawn-time.patch b/leaf-server/minecraft-patches/features/0277-Rewrite-entity-despawn-time.patch
index 43d09977e0..9cdaa0bc40 100644
--- a/leaf-server/minecraft-patches/features/0277-Rewrite-entity-despawn-time.patch
+++ b/leaf-server/minecraft-patches/features/0277-Rewrite-entity-despawn-time.patch
@@ -109,10 +109,10 @@ index 6d6b1a261708b88a4b706ccc51b7d397fe42ea6b..f27f198e84ac668446c9d57772343ba1
private final ChunkEntitySlices[] slices = new ChunkEntitySlices[REGION_SIZE * REGION_SIZE];
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 2423ddfdb06bc479233c9a7ec66ad3a70a6ef666..1b7ae1da1fcd0d5535ea37120aada9772334c8b2 100644
+index 231925545b6543004a0f6164bf73ee7139ad86f6..6bad7fa23cfd3be43f6f642ae3eeb02492b0aa2f 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1148,6 +1148,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1131,6 +1131,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
this.debugSynchronizers.tick(this.server.debugSubscribers());
profiler.pop();
this.environmentAttributes().invalidateTickCache();
@@ -120,7 +120,7 @@ index 2423ddfdb06bc479233c9a7ec66ad3a70a6ef666..1b7ae1da1fcd0d5535ea37120aada977
if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { this.leaf$asyncTracker.onEntitiesTickEnd(); } // Leaf - Multithreaded tracker
}
-@@ -1666,7 +1667,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1649,7 +1650,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
entity.setOldPosAndRot();
ProfilerFiller profiler = Profiler.get();
entity.tickCount++;
@@ -129,7 +129,7 @@ index 2423ddfdb06bc479233c9a7ec66ad3a70a6ef666..1b7ae1da1fcd0d5535ea37120aada977
profiler.push(entity.typeHolder()::getRegisteredName);
profiler.incrementCounter("tickNonPassenger");
final boolean isActive = io.papermc.paper.entity.activation.ActivationRange.checkIfActive(entity); // Paper - EAR 2
-@@ -1687,7 +1688,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1670,7 +1671,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
} else if (entity instanceof Player || this.entityTickList.contains(entity)) {
entity.setOldPosAndRot();
entity.tickCount++;
diff --git a/leaf-server/minecraft-patches/features/0279-Cache-world-border.patch b/leaf-server/minecraft-patches/features/0279-Cache-world-border.patch
index 21a54087a6..b14ffbd3d1 100644
--- a/leaf-server/minecraft-patches/features/0279-Cache-world-border.patch
+++ b/leaf-server/minecraft-patches/features/0279-Cache-world-border.patch
@@ -7,7 +7,7 @@ The world border is only initialized once when the level is created.
Lookup from data storage map multiple time is quite unnecessary and expensive.
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 1b7ae1da1fcd0d5535ea37120aada9772334c8b2..0452d2fb484b914a4beb5af5d2a341a406f776e5 100644
+index 6bad7fa23cfd3be43f6f642ae3eeb02492b0aa2f..2fac5a4fd19a5f3d9b832e7611f005b852eec9fa 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -245,6 +245,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@@ -18,7 +18,7 @@ index 1b7ae1da1fcd0d5535ea37120aada9772334c8b2..0452d2fb484b914a4beb5af5d2a341a4
@Override
public @Nullable LevelChunk getChunkIfLoaded(int x, int z) {
-@@ -2594,8 +2595,14 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -2577,8 +2578,14 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
@Override
public WorldBorder getWorldBorder() {
diff --git a/leaf-server/minecraft-patches/features/0283-Reduce-optimiseRandomTick-new-BlockPos-instance-crea.patch b/leaf-server/minecraft-patches/features/0283-Reduce-optimiseRandomTick-new-BlockPos-instance-crea.patch
index 8ac6950ea0..00a23d5282 100644
--- a/leaf-server/minecraft-patches/features/0283-Reduce-optimiseRandomTick-new-BlockPos-instance-crea.patch
+++ b/leaf-server/minecraft-patches/features/0283-Reduce-optimiseRandomTick-new-BlockPos-instance-crea.patch
@@ -30,7 +30,7 @@ diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/
index 2fac5a4fd19a5f3d9b832e7611f005b852eec9fa..d577ddcdeaa0e102f0b09955b77b20fe3854140d 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1182,6 +1182,8 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1165,6 +1165,8 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
this.players.stream().filter(LivingEntity::isSleeping).collect(Collectors.toList()).forEach(player -> player.stopSleepInBed(false, false));
}
@@ -39,7 +39,7 @@ index 2fac5a4fd19a5f3d9b832e7611f005b852eec9fa..d577ddcdeaa0e102f0b09955b77b20fe
// Paper start - optimise random ticking
private void optimiseRandomTick(final LevelChunk chunk, final int tickSpeed) {
final LevelChunkSection[] sections = chunk.getSections();
-@@ -1215,14 +1217,16 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1198,14 +1200,16 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
final int location = (int)tickList.getRaw(index) & 0xFFFF;
final BlockState state = states.get(location);
diff --git a/leaf-server/minecraft-patches/features/0299-configurable-ice-and-snow-tick-chance.patch b/leaf-server/minecraft-patches/features/0299-configurable-ice-and-snow-tick-chance.patch
index b72169a634..3a719d98a5 100644
--- a/leaf-server/minecraft-patches/features/0299-configurable-ice-and-snow-tick-chance.patch
+++ b/leaf-server/minecraft-patches/features/0299-configurable-ice-and-snow-tick-chance.patch
@@ -5,10 +5,10 @@ Subject: [PATCH] configurable ice and snow tick chance
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 64c39eb7d15c08a0b61ce4c469c1ee65ff1456f0..f7f82e4904771cedc17ffc5f5d5596b4e528668a 100644
+index fa2a7e7369afa4396bd5a7a955662e2eb6ddee93..008e2b04434f95879fbe8e85a6f4ab874d087803 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1281,9 +1281,10 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1264,9 +1264,10 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
ProfilerFiller profiler = Profiler.get();
profiler.push("iceandsnow");
diff --git a/leaf-server/minecraft-patches/features/0302-disable-world-data-saving.patch b/leaf-server/minecraft-patches/features/0302-disable-world-data-saving.patch
index c8ff09731a..2f80c66c67 100644
--- a/leaf-server/minecraft-patches/features/0302-disable-world-data-saving.patch
+++ b/leaf-server/minecraft-patches/features/0302-disable-world-data-saving.patch
@@ -50,10 +50,10 @@ index 9a9a599ef178f851ee5c783631a724013a693586..f950472f371af3581af82c5bcdd7a800
final CompoundTag save = poi.save();
poi.setDirty(false);
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index c6540de512d577ffe5ba7bd3757f43deb540f95c..d9526a1bfa6a33adec3633b5b7bc667a1fd942ca 100644
+index 008e2b04434f95879fbe8e85a6f4ab874d087803..9f498dbcabc9badc337c6039ddc1bd7574709298 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1769,6 +1769,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1752,6 +1752,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
}
// Paper start - Incremental chunk and player saving
public void saveIncrementally(final boolean doFull) {
@@ -61,7 +61,7 @@ index c6540de512d577ffe5ba7bd3757f43deb540f95c..d9526a1bfa6a33adec3633b5b7bc667a
if (doFull) {
org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(this.getWorld()));
this.saveLevelData(false);
-@@ -1784,6 +1785,18 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1767,6 +1768,18 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
public void save(final @Nullable ProgressListener progressListener, final boolean flush, final boolean noSave, final boolean close) {
// Paper end - add close param
ServerChunkCache chunkSource = this.getChunkSource();
diff --git a/leaf-server/minecraft-patches/features/0304-Leaves-Lithium-Sleeping-Block-Entity.patch b/leaf-server/minecraft-patches/features/0304-Leaves-Lithium-Sleeping-Block-Entity.patch
index ac6bf79408..7fd18e1556 100644
--- a/leaf-server/minecraft-patches/features/0304-Leaves-Lithium-Sleeping-Block-Entity.patch
+++ b/leaf-server/minecraft-patches/features/0304-Leaves-Lithium-Sleeping-Block-Entity.patch
@@ -54,10 +54,10 @@ index f2c5a6ce769d8a5fdb4b836eae6a61a4da91cb3f..c6ce0318e27ca49d9bb68f425ab92089
}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index d9526a1bfa6a33adec3633b5b7bc667a1fd942ca..095931e77a2fc64ac8165a2e5281b0d7599711dd 100644
+index 9f498dbcabc9badc337c6039ddc1bd7574709298..ecec4d70ddbaa63ab96bbb664beccd3f1c8dea46 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -2900,6 +2900,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -2883,6 +2883,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
for (TickingBlockEntity ticker : this.blockEntityTickers) {
BlockPos blockPos = ticker.getPos();
@@ -281,10 +281,10 @@ index 27122aa287626a7deb70bad5b601fe086e268b82..006a3c8bf13d93570e264added86a179
}
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index 7224f2a8968745bb7231cf78a1d6f2da53656a01..dc4ec06a7f407a438fd816af66915bb58266a7f0 100644
+index 90009387d495db3fb3f68cd7d1ebe469b9aa1c9a..6e456da74355646a649473637386a391e98f1cc5 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
-@@ -2318,4 +2318,25 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -2308,4 +2308,25 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
return getWorld().getEnvironment() == org.bukkit.World.Environment.THE_END;
}
// Purpur end - Add allow water in end world option
diff --git a/leaf-server/minecraft-patches/features/0305-fixup-Leaves-Lithium-Sleeping-Block-Entity.patch b/leaf-server/minecraft-patches/features/0305-fixup-Leaves-Lithium-Sleeping-Block-Entity.patch
index fdec5ee0d9..ef8ca4129a 100644
--- a/leaf-server/minecraft-patches/features/0305-fixup-Leaves-Lithium-Sleeping-Block-Entity.patch
+++ b/leaf-server/minecraft-patches/features/0305-fixup-Leaves-Lithium-Sleeping-Block-Entity.patch
@@ -25,10 +25,10 @@ https://github.com/CaffeineMC/lithium/commit/ba08831d0254076cba1e8aba3db6c42f26d
https://github.com/CaffeineMC/lithium/commit/ba089f52be5b26e6590402211581e05e467a8e2d
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 095931e77a2fc64ac8165a2e5281b0d7599711dd..799b1834fda5325247efdef9f56c7209ac899cd7 100644
+index ecec4d70ddbaa63ab96bbb664beccd3f1c8dea46..d3312527c47df39d91300ff9b915d23bbee027d4 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -2900,7 +2900,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -2883,7 +2883,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
for (TickingBlockEntity ticker : this.blockEntityTickers) {
BlockPos blockPos = ticker.getPos();
diff --git a/leaf-server/minecraft-patches/features/0309-Add-read-only-mode-for-Linear-v2.patch b/leaf-server/minecraft-patches/features/0309-Add-read-only-mode-for-Linear-v2.patch
index 35601c9953..433d1bc7e0 100644
--- a/leaf-server/minecraft-patches/features/0309-Add-read-only-mode-for-Linear-v2.patch
+++ b/leaf-server/minecraft-patches/features/0309-Add-read-only-mode-for-Linear-v2.patch
@@ -47,10 +47,10 @@ index 5a8937740f7779f971c2a13ec580eed7bcc26395..98b48a8a6f6372114ee1f71b2c69bff9
try {
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 799b1834fda5325247efdef9f56c7209ac899cd7..64403c9c41098b0d28192e87e273cf81e49b4ec6 100644
+index d3312527c47df39d91300ff9b915d23bbee027d4..20dbe3ae363cafa853d1f54ae15458348629dd18 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1770,6 +1770,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1753,6 +1753,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
// Paper start - Incremental chunk and player saving
public void saveIncrementally(final boolean doFull) {
if (org.dreeam.leaf.config.modules.misc.DisableWorldDataSaving.shouldSkipSave(this)) return; // Leaf - disable world data saving
@@ -58,7 +58,7 @@ index 799b1834fda5325247efdef9f56c7209ac899cd7..64403c9c41098b0d28192e87e273cf81
if (doFull) {
org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(this.getWorld()));
this.saveLevelData(false);
-@@ -1786,7 +1787,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1769,7 +1770,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
// Paper end - add close param
ServerChunkCache chunkSource = this.getChunkSource();
// Leaf start - disable world data saving
diff --git a/leaf-server/minecraft-patches/features/0321-optimize-entity-activation.patch b/leaf-server/minecraft-patches/features/0321-optimize-entity-activation.patch
index 04e7c924ba..67d37affa5 100644
--- a/leaf-server/minecraft-patches/features/0321-optimize-entity-activation.patch
+++ b/leaf-server/minecraft-patches/features/0321-optimize-entity-activation.patch
@@ -164,10 +164,10 @@ index c4194b1921f4741b5452259c0ab87fd94977c7f3..4d5c38c0fb6d18026f84be5eccf87d78
}
}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
-index 64403c9c41098b0d28192e87e273cf81e49b4ec6..3739eaf02c4aa74108b732f8a9c7bcbd0fdbbae2 100644
+index 20dbe3ae363cafa853d1f54ae15458348629dd18..ba589ac63ebba99679e4d20a5f455da85941a44f 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
-@@ -1116,13 +1116,13 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1099,13 +1099,13 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
profiler.pop();
}
@@ -183,7 +183,7 @@ index 64403c9c41098b0d28192e87e273cf81e49b4ec6..3739eaf02c4aa74108b732f8a9c7bcbd
if (!entity.isRemoved()) {
if (!tickRateManager.isEntityFrozen(entity)) {
profiler.push("checkDespawn");
-@@ -1273,6 +1273,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+@@ -1256,6 +1256,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
public final org.dreeam.leaf.world.RandomTickSystem randomTickSystem = new org.dreeam.leaf.world.RandomTickSystem(); // Leaf - optimize random tick
public final org.dreeam.leaf.world.EntityCollisionCache entityCollisionCache = new org.dreeam.leaf.world.EntityCollisionCache(); // Leaf - cache collision list
public final org.dreeam.leaf.util.FastBitRadixSort fastBitRadixSort = new org.dreeam.leaf.util.FastBitRadixSort(); // Leaf - fast bit radix sort
diff --git a/leaf-server/minecraft-patches/features/0326-Limit-pushable-entity-queries.patch b/leaf-server/minecraft-patches/features/0326-Limit-pushable-entity-queries.patch
index 892ebac3eb..60aa179118 100644
--- a/leaf-server/minecraft-patches/features/0326-Limit-pushable-entity-queries.patch
+++ b/leaf-server/minecraft-patches/features/0326-Limit-pushable-entity-queries.patch
@@ -255,10 +255,10 @@ index af775113bebde3b49ded7d870e75419514b6e361..14cf550c114df95fd43a5e002715c34e
// Leaf end - Only player pushable
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index dc4ec06a7f407a438fd816af66915bb58266a7f0..048f4d0f0ce0166059781835c1590ac7ed49f934 100644
+index 6e456da74355646a649473637386a391e98f1cc5..dcc783fe5aa1b4790db5873e22b32ea4517d68df 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
-@@ -1885,6 +1885,29 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1875,6 +1875,29 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
// Paper end - rewrite chunk system
}
diff --git a/leaf-server/minecraft-patches/features/0327-Leaf-config-changes.patch b/leaf-server/minecraft-patches/features/0327-Leaf-config-changes.patch
new file mode 100644
index 0000000000..120541851f
--- /dev/null
+++ b/leaf-server/minecraft-patches/features/0327-Leaf-config-changes.patch
@@ -0,0 +1,71 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com>
+Date: Wed, 12 Oct 2022 10:42:15 -0400
+Subject: [PATCH] Leaf config changes
+
+
+diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
+index ba589ac63ebba99679e4d20a5f455da85941a44f..3739eaf02c4aa74108b732f8a9c7bcbd0fdbbae2 100644
+--- a/net/minecraft/server/level/ServerLevel.java
++++ b/net/minecraft/server/level/ServerLevel.java
+@@ -681,7 +681,24 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet
+ savedDataStorage.set(io.papermc.paper.world.saveddata.PaperWorldPDC.TYPE, loadedWorldData.pdc() == null ? io.papermc.paper.world.saveddata.PaperWorldPDC.TYPE.constructor().get() : loadedWorldData.pdc());
+ final GameRules gameRules = new GameRules(server.getWorldData().enabledFeatures(), savedDataStorage.computeIfAbsent(net.minecraft.world.level.gamerules.GameRuleMap.TYPE));
+ this.gameRules = gameRules;
+- super(levelData, dimension, server.registryAccess(), levelStem.type(), false, isDebug, biomeZoomSeed, server.getMaxChainedNeighborUpdates(), loadedWorldData.bukkitName(), gen, biomeProvider, env, spigotConfig -> server.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(server.storageSource.getDimensionPath(dimension), dimension.identifier(), spigotConfig, server.registryAccess(), gameRules)), spigotConfig -> server.galeConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(server.storageSource.getDimensionPath(dimension), dimension.identifier(), spigotConfig, server.registryAccess(), gameRules)), executor); // Paper - create paper world configs // Paper - Anti-Xray - Pass executor // Gale - Gale configuration
++ super(
++ levelData,
++ dimension,
++ server.registryAccess(),
++ levelStem.type(),
++ false,
++ isDebug,
++ biomeZoomSeed,
++ server.getMaxChainedNeighborUpdates(),
++ loadedWorldData.bukkitName(),
++ gen,
++ biomeProvider,
++ env,
++ spigotConfig -> server.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(server.storageSource.getDimensionPath(dimension), dimension.identifier(), spigotConfig, server.registryAccess(), gameRules)), // Paper - create paper world configs
++ spigotConfig -> server.galeConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(server.storageSource.getDimensionPath(dimension), dimension.identifier(), spigotConfig, server.registryAccess(), gameRules)), // Gale - Gale configuration
++ spigotConfig -> org.dreeam.leaf.config.LeafConfig.createWorldConfig(server.storageSource.getDimensionPath(dimension)), // Leaf - per-world configuration
++ executor
++ );
+ this.weatherData = savedDataStorage.computeIfAbsent(WeatherData.TYPE);
+ this.weatherData.setLevel(this);
+ this.typeKey = typeKey;
+diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
+index dcc783fe5aa1b4790db5873e22b32ea4517d68df..048f4d0f0ce0166059781835c1590ac7ed49f934 100644
+--- a/net/minecraft/world/level/Level.java
++++ b/net/minecraft/world/level/Level.java
+@@ -179,6 +179,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+ }
+ // Gale end - Gale configuration
+
++ // Leaf start - per-world configuration
++ private final org.dreeam.leaf.config.LeafWorldConfig leafConfig;
++ public org.dreeam.leaf.config.LeafWorldConfig leafConfig() {
++ return this.leafConfig;
++ }
++ // Leaf end - per-world configuration
++
+ public final org.purpurmc.purpur.PurpurWorldConfig purpurConfig; // Purpur - Purpur config files
+ public final io.papermc.paper.redstone.RedstoneWireTurbo turbo; // Leaf - SparklyPaper - parallel world ticking - moved to world
+ public static @Nullable BlockPos lastPhysicsProblem; // Spigot
+@@ -895,6 +902,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+ io.papermc.paper.configuration.WorldConfiguration> paperWorldConfigCreator, // Paper - create paper world config
+ java.util.function.Function galeWorldConfigCreator, // Gale - Gale configuration
++ java.util.function.Function leafWorldConfigCreator, // Leaf - per-world configuration
+ java.util.concurrent.Executor executor // Paper - Anti-Xray
+ ) {
+ // Paper start - getblock optimisations - cache world height/sections
+@@ -911,6 +920,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+ this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config
+ this.purpurConfig = new org.purpurmc.purpur.PurpurWorldConfig(bukkitName, environment, worldKey); // Purpur - Purpur config files
+ this.galeConfig = galeWorldConfigCreator.apply(this.spigotConfig); // Gale - Gale configuration
++ this.leafConfig = leafWorldConfigCreator.apply(this.spigotConfig); // Leaf - per-world configuration
+ this.playerBreedingCooldowns = this.getNewBreedingCooldownCache(); // Purpur - Add adjustable breeding cooldown to config
+ this.generator = generator;
+ this.world = new CraftWorld((ServerLevel) this, worldKey, biomeProvider, environment);
From be435d14b737201c5f69c3c335253bec7d378731 Mon Sep 17 00:00:00 2001
From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com>
Date: Thu, 13 Aug 2026 02:18:02 +0800
Subject: [PATCH 04/14] Some work
---
.../org/dreeam/leaf/config/ConfigBinder.java | 14 +-
.../org/dreeam/leaf/config/ConfigModule.java | 15 +-
.../leaf/config/ConfigModuleLoader.java | 29 ++--
.../org/dreeam/leaf/config/LeafConfig.java | 41 +++--
.../leaf/config/LeafConfigMigration.java | 150 ++++++++++++++++++
.../dreeam/leaf/config/LeafWorldConfig.java | 2 +-
.../dreeam/leaf/config/WorldConfigModule.java | 7 +-
.../config/annotations/ConfigClassInfo.java | 4 +-
.../annotations/HotReloadUnsupported.java | 4 +-
9 files changed, 224 insertions(+), 42 deletions(-)
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigMigration.java
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigBinder.java b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigBinder.java
index b8264a0ce3..af48647e78 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigBinder.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigBinder.java
@@ -36,7 +36,7 @@ static void bindWorld(
}
private static void bind(
- ConfigModule module,
+ Object module,
LeafConfigAccessor config,
boolean global,
boolean alreadyInitialized
@@ -54,11 +54,15 @@ private static void bind(
config.addComment(basePath, classInfo.comments());
}
+ boolean skipModuleReload = alreadyInitialized
+ && moduleClass.isAnnotationPresent(HotReloadUnsupported.class);
+
for (Field field : moduleClass.getDeclaredFields()) {
boolean skipLoad = field.getAnnotation(DoNotLoad.class) != null;
- boolean doNotReload = alreadyInitialized
- && field.getAnnotation(HotReloadUnsupported.class) != null;
+ boolean skipReload = skipModuleReload
+ || alreadyInitialized && field.getAnnotation(HotReloadUnsupported.class) != null;
ConfigInfo configInfo = field.getAnnotation(ConfigInfo.class);
+
if (skipLoad || configInfo == null) {
continue;
}
@@ -68,9 +72,11 @@ private static void bind(
Object target = global ? null : module;
Object defaultValue = field.get(target);
+ // Always call readValue, to keep comments on reloading
Object loadedValue = readValue(config, path(basePath, configInfo), configInfo.comments(),
field, defaultValue);
- if (!doNotReload) {
+
+ if (!skipReload) {
field.set(target, loadedValue);
}
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModule.java b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModule.java
index 08592932d4..5588c74107 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModule.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModule.java
@@ -5,14 +5,22 @@
/**
* Marker and lifecycle contract for a server-wide Leaf configuration module.
*
- * Annotated global module fields must be static and mutable. World-scoped modules must
- * implement {@link WorldConfigModule} instead and use instance fields.
+ * Annotated fields must be static and mutable.
*/
public interface ConfigModule {
+ /**
+ * Runs after this module's configuration fields have been loaded.
+ *
+ * This hook runs during both initial configuration loading and reload. Core registries
+ * are not guaranteed to be available during the initial invocation.
+ */
default void onLoaded() {
}
+ /**
+ * Runs after this module's configuration fields have been loaded and core registries are available.
+ */
default void onPostLoaded() {
}
@@ -29,7 +37,4 @@ static void clearModules() {
ConfigModuleLoader.clearModules();
}
- static void loadWorldModules(LeafWorldConfig config) {
- ConfigModuleLoader.loadWorldModules(config);
- }
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModuleLoader.java b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModuleLoader.java
index ba5849ff0d..7d3f0bfe61 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModuleLoader.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModuleLoader.java
@@ -33,8 +33,7 @@ static void initModules()
ObjectArrays.quickSort(classes, Comparator.comparing((Class> clazz) -> clazz.getSimpleName())
.thenComparing(Class::getName));
for (Class> moduleClass : classes) {
- if (!ConfigModule.class.isAssignableFrom(moduleClass)
- || moduleClass.isInterface()
+ if (moduleClass.isInterface()
|| Modifier.isAbstract(moduleClass.getModifiers())) {
continue;
}
@@ -47,6 +46,9 @@ static void initModules()
validateAnnotatedModule(moduleClass);
continue;
}
+ if (!ConfigModule.class.isAssignableFrom(moduleClass)) {
+ continue;
+ }
ConfigModule module = (ConfigModule) moduleClass.getConstructor().newInstance();
validateAnnotatedModule(moduleClass);
@@ -88,20 +90,27 @@ static void loadWorldModules(LeafWorldConfig config) {
try {
boolean alreadyInitialized = config.isReload();
for (Class extends WorldConfigModule> moduleClass : worldModules) {
- WorldConfigModule module = alreadyInitialized ? config.reloadModule(moduleClass) : null;
- if (module == null) {
- module = moduleClass.getConstructor().newInstance();
- }
-
- ConfigBinder.bindWorld(module, config, alreadyInitialized);
- config.registerModule(moduleClass, module);
- module.onLoaded();
+ loadWorldModule(config, moduleClass, alreadyInitialized);
}
} catch (ReflectiveOperationException exception) {
throw new RuntimeException("Could not load Leaf world configuration modules", exception);
}
}
+ private static void loadWorldModule(
+ LeafWorldConfig config,
+ Class moduleClass,
+ boolean alreadyInitialized
+ ) throws ReflectiveOperationException {
+ T module = alreadyInitialized ? config.reloadModule(moduleClass) : null;
+ if (module == null) {
+ module = moduleClass.getConstructor().newInstance();
+ }
+
+ ConfigBinder.bindWorld(module, config, alreadyInitialized);
+ config.registerModule(moduleClass, module);
+ }
+
static void clearModules() {
LOADED_MODULES.clear();
worldModules = List.of();
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java
index ca5672a807..a9960510fb 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java
@@ -106,15 +106,19 @@ private static void loadConfig(boolean init) throws Exception {
// Create config folder
createDirectory(CONFIG_DIRECTORY);
+ File globalConfigFile = new File(CONFIG_DIRECTORY, GLOBAL_CONFIG_FILE);
+ File worldDefaultsFile = new File(CONFIG_DIRECTORY, DEFAULT_WORLD_CONFIG_FILE);
+
+ // Read and migrate existing raw values before either config applies defaults.
+ LeafConfigMigration.migrate(globalConfigFile, worldDefaultsFile);
+
globalConfig = new LeafGlobalConfig(init);
// Load config modules
ConfigModule.initModules();
- File worldDefaultsFile = new File(CONFIG_DIRECTORY, DEFAULT_WORLD_CONFIG_FILE);
if (!worldDefaultsFile.exists()) {
- globalConfig.saveConfig();
- Files.copy(new File(CONFIG_DIRECTORY, GLOBAL_CONFIG_FILE).toPath(), worldDefaultsFile.toPath());
+ Files.createFile(worldDefaultsFile.toPath());
}
LeafWorldConfig previousWorldDefaults = worldDefaultsConfig;
worldDefaultsConfig = LeafWorldConfig.loadDefaults(worldDefaultsFile, previousWorldDefaults);
@@ -252,17 +256,7 @@ private static void findClassesInPackageByJar(String packageName, Enumeration components) implements Comparable {
private static ConfigVersion initial() {
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigMigration.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigMigration.java
new file mode 100644
index 0000000000..8cfe3ea93a
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigMigration.java
@@ -0,0 +1,150 @@
+package org.dreeam.leaf.config;
+
+import io.github.thatsmusic99.configurationmaster.api.ConfigFile;
+import io.github.thatsmusic99.configurationmaster.api.ConfigSection;
+
+import java.io.File;
+import java.util.Objects;
+
+/**
+ * Applies versioned migrations to raw Leaf configuration files before defaults are added.
+ */
+final class LeafConfigMigration {
+
+ private LeafConfigMigration() {
+ }
+
+ static void migrate(File globalFile, File worldDefaultsFile) throws Exception {
+ if (!globalFile.isFile()) {
+ return;
+ }
+
+ ConfigFile globalConfig = ConfigFile.loadConfig(globalFile);
+ String storedVersion = globalConfig.getString("config-version", null);
+ MigrationContext context = new MigrationContext(globalConfig, worldDefaultsFile);
+
+ applyMigrations(storedVersion, context);
+
+ context.saveChanges();
+ }
+
+ private static void applyMigrations(String storedVersion, MigrationContext context) throws Exception {
+ /*
+ * Add migrations here, grouped by the config version that introduced the new path.
+ *
+ * if (LeafConfig.isConfigVersionBefore(storedVersion, "3.1")) {
+ * context.migrate(
+ * ConfigFileType.GLOBAL, "old.path",
+ * ConfigFileType.WORLD_DEFAULTS, "new.path"
+ * );
+ * }
+ */
+ }
+
+ private enum ConfigFileType {
+ GLOBAL,
+ WORLD_DEFAULTS
+ }
+
+ private static final class MigrationContext {
+
+ private final ConfigFile globalConfig;
+ private final File worldDefaultsFile;
+ private ConfigFile worldDefaultsConfig;
+ private boolean globalChanged;
+ private boolean worldDefaultsChanged;
+
+ private MigrationContext(ConfigFile globalConfig, File worldDefaultsFile) {
+ this.globalConfig = globalConfig;
+ this.worldDefaultsFile = worldDefaultsFile;
+ }
+
+ private void migrate(
+ ConfigFileType source,
+ String oldPath,
+ ConfigFileType target,
+ String newPath
+ ) throws Exception {
+ validateMigration(source, oldPath, target, newPath);
+
+ ConfigFile sourceConfig = config(source, false);
+ if (sourceConfig == null || !sourceConfig.contains(oldPath)) {
+ return;
+ }
+
+ Object oldValue = sourceConfig.get(oldPath);
+ if (oldValue == null || oldValue instanceof ConfigSection) {
+ throw new IllegalStateException("Legacy config path must point to an option: "
+ + source + ":" + oldPath);
+ }
+
+ ConfigFile targetConfig = config(target, true);
+ if (targetConfig.contains(newPath)) {
+ sourceConfig.set(oldPath, null);
+ } else {
+ sourceConfig.moveTo(oldPath, newPath, targetConfig);
+ }
+
+ markChanged(source);
+ markChanged(target);
+ }
+
+ private static void validateMigration(
+ ConfigFileType source,
+ String oldPath,
+ ConfigFileType target,
+ String newPath
+ ) {
+ Objects.requireNonNull(source, "source");
+ requirePath(oldPath, "oldPath");
+ Objects.requireNonNull(target, "target");
+ requirePath(newPath, "newPath");
+
+ if (source == target && oldPath.equals(newPath)) {
+ throw new IllegalArgumentException("A migration must change the file or config path");
+ }
+ if (source == target
+ && (oldPath.startsWith(newPath + ".") || newPath.startsWith(oldPath + "."))) {
+ throw new IllegalArgumentException("Paths in the same config file must not overlap");
+ }
+ }
+
+ private static void requirePath(String path, String name) {
+ Objects.requireNonNull(path, name);
+ if (path.isBlank()) {
+ throw new IllegalArgumentException(name + " must not be blank");
+ }
+ }
+
+ private ConfigFile config(ConfigFileType type, boolean create) throws Exception {
+ if (type == ConfigFileType.GLOBAL) {
+ return this.globalConfig;
+ }
+ if (this.worldDefaultsConfig != null) {
+ return this.worldDefaultsConfig;
+ }
+ if (!create && !this.worldDefaultsFile.isFile()) {
+ return null;
+ }
+ this.worldDefaultsConfig = ConfigFile.loadConfig(this.worldDefaultsFile);
+ return this.worldDefaultsConfig;
+ }
+
+ private void markChanged(ConfigFileType type) {
+ if (type == ConfigFileType.GLOBAL) {
+ this.globalChanged = true;
+ } else {
+ this.worldDefaultsChanged = true;
+ }
+ }
+
+ private void saveChanges() throws Exception {
+ if (this.globalChanged) {
+ this.globalConfig.save();
+ }
+ if (this.worldDefaultsChanged) {
+ this.worldDefaultsConfig.save();
+ }
+ }
+ }
+}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafWorldConfig.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafWorldConfig.java
index 7a479caf2e..094dfab9c1 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafWorldConfig.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafWorldConfig.java
@@ -37,7 +37,7 @@ private LeafWorldConfig(File file, LeafWorldConfig defaults, LeafWorldConfig rel
this.defaults = defaults;
this.reloadSource = reloadSource;
try {
- ConfigModule.loadWorldModules(this);
+ ConfigModuleLoader.loadWorldModules(this);
} finally {
this.reloadSource = null;
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/WorldConfigModule.java b/leaf-server/src/main/java/org/dreeam/leaf/config/WorldConfigModule.java
index a584827ab0..c5adce0071 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/WorldConfigModule.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/WorldConfigModule.java
@@ -1,10 +1,9 @@
package org.dreeam.leaf.config;
/**
- * Marker and lifecycle contract for a world-scoped Leaf configuration module.
+ * Marker for a world-scoped Leaf configuration module.
*
- * Annotated fields must be mutable instance fields. A separate module instance is created
- * for the defaults and for every world override.
+ * Annotated fields must be mutable instance fields.
*/
-public interface WorldConfigModule extends ConfigModule {
+public interface WorldConfigModule {
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/ConfigClassInfo.java b/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/ConfigClassInfo.java
index c429c9fbde..dadbb94026 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/ConfigClassInfo.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/ConfigClassInfo.java
@@ -1,6 +1,6 @@
package org.dreeam.leaf.config.annotations;
-import org.dreeam.leaf.config.EnumConfigCategory;
+import org.dreeam.leaf.config.ConfigCategory;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
@@ -13,7 +13,7 @@
@Target(ElementType.TYPE)
public @interface ConfigClassInfo {
- EnumConfigCategory category();
+ ConfigCategory category();
String name();
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/HotReloadUnsupported.java b/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/HotReloadUnsupported.java
index c4dbdcf1c1..717407f8a6 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/HotReloadUnsupported.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/HotReloadUnsupported.java
@@ -6,9 +6,9 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-/** Keeps an annotated configuration field unchanged during a hot reload. */
+/** Prevents an annotated field, or every configuration field in an annotated module, from being reloaded. */
@Documented
@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.FIELD)
+@Target({ElementType.FIELD, ElementType.TYPE})
public @interface HotReloadUnsupported {
}
From 8d917d2a42dfc6dc0ed12ad152ee163648756f66 Mon Sep 17 00:00:00 2001
From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com>
Date: Thu, 13 Aug 2026 04:36:18 +0800
Subject: [PATCH 05/14] Work
---
.../main/java/org/dreeam/leaf/config/ConfigBinder.java | 9 +++++----
.../java/org/dreeam/leaf/config/LeafConfigAccessor.java | 4 ++--
.../dreeam/leaf/config/annotations/ConfigClassInfo.java | 2 +-
.../org/dreeam/leaf/config/annotations/ConfigInfo.java | 2 +-
.../org/dreeam/leaf/config/annotations/DoNotLoad.java | 6 +++++-
5 files changed, 14 insertions(+), 9 deletions(-)
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigBinder.java b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigBinder.java
index af48647e78..0c2d99c37b 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigBinder.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigBinder.java
@@ -49,9 +49,10 @@ private static void bind(
}
String basePath = basePath(classInfo);
- if (!classInfo.comments().isBlank()
+ String sectionComment = config.pickStringRegionBased(classInfo.comments());
+ if (!sectionComment.isBlank()
&& (!(config instanceof LeafWorldConfig worldConfig) || worldConfig.isDefaultsConfig())) {
- config.addComment(basePath, classInfo.comments());
+ config.addComment(basePath, sectionComment);
}
boolean skipModuleReload = alreadyInitialized
@@ -72,9 +73,9 @@ private static void bind(
Object target = global ? null : module;
Object defaultValue = field.get(target);
+ String comment = config.pickStringRegionBased(configInfo.comments());
// Always call readValue, to keep comments on reloading
- Object loadedValue = readValue(config, path(basePath, configInfo), configInfo.comments(),
- field, defaultValue);
+ Object loadedValue = readValue(config, path(basePath, configInfo), comment, field, defaultValue);
if (!skipReload) {
field.set(target, loadedValue);
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigAccessor.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigAccessor.java
index 85ac8a57fd..e1d6091680 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigAccessor.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigAccessor.java
@@ -171,7 +171,7 @@ public void addCommentRegionBased(String path, String en, String cn) {
configFile.addComment(path, LeafConfig.isChineseLocale() ? cn : en);
}
- public String pickStringRegionBased(String en, String cn) {
- return LeafConfig.isChineseLocale() ? cn : en;
+ public String pickStringRegionBased(String[] localizedStrings) {
+ return LeafConfig.isChineseLocale() ? localizedStrings[1] : localizedStrings[0];
}
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/ConfigClassInfo.java b/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/ConfigClassInfo.java
index dadbb94026..b59a2b9938 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/ConfigClassInfo.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/ConfigClassInfo.java
@@ -19,5 +19,5 @@
String[] directory() default {};
- String comments() default "";
+ String[] comments() default {};
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/ConfigInfo.java b/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/ConfigInfo.java
index 564e0bd5de..022ce8996d 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/ConfigInfo.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/ConfigInfo.java
@@ -15,5 +15,5 @@
String[] directory() default {};
- String comments() default "";
+ String[] comments() default {};
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/DoNotLoad.java b/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/DoNotLoad.java
index cd5f7da6bc..58d48e9971 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/DoNotLoad.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/DoNotLoad.java
@@ -7,7 +7,11 @@
import java.lang.annotation.Target;
/**
- * Marks a runtime-only or derived field that must never be read from or written to configuration.
+ * Marks a non-configuration field whose value is initialized by module lifecycle hooks from
+ * already loaded configuration fields.
+ *
+ * Fields annotated with {@code DoNotLoad} are never read from or written to configuration
+ * and should not be annotated with {@link ConfigInfo}.
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
From 1381c2dd63194761967723353670be60a1afd9dd Mon Sep 17 00:00:00 2001
From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com>
Date: Thu, 13 Aug 2026 10:33:41 +0800
Subject: [PATCH 06/14] Basic part of config refacor finished
---
.../org/dreeam/leaf/config/ConfigBinder.java | 51 +-
.../org/dreeam/leaf/config/ConfigModule.java | 1 -
.../leaf/config/ConfigModuleLoader.java | 1 +
.../leaf/config/ConfigPathResolver.java | 29 ++
.../org/dreeam/leaf/config/LeafConfig.java | 60 ++-
.../leaf/config/LeafConfigAccessor.java | 11 +-
.../leaf/config/LeafConfigMigration.java | 63 +--
.../dreeam/leaf/config/LeafGlobalConfig.java | 19 +-
.../dreeam/leaf/config/LeafWorldConfig.java | 39 +-
.../config/annotations/ConfigClassInfo.java | 7 +-
.../leaf/config/annotations/ConfigInfo.java | 7 +-
.../migration/gale/GaleConfigMigration.java | 464 ++++++++++++++++++
.../org/dreeam/leaf/config/package-info.java | 2 +
13 files changed, 629 insertions(+), 125 deletions(-)
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/ConfigPathResolver.java
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/migration/gale/GaleConfigMigration.java
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/package-info.java
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigBinder.java b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigBinder.java
index 0c2d99c37b..1dcf926b38 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigBinder.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigBinder.java
@@ -4,10 +4,10 @@
import org.dreeam.leaf.config.annotations.ConfigInfo;
import org.dreeam.leaf.config.annotations.DoNotLoad;
import org.dreeam.leaf.config.annotations.HotReloadUnsupported;
+import org.jspecify.annotations.Nullable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
-import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@@ -48,10 +48,9 @@ private static void bind(
+ " is missing @ConfigClassInfo");
}
- String basePath = basePath(classInfo);
+ String basePath = ConfigPathResolver.modulePath(moduleClass);
String sectionComment = config.pickStringRegionBased(classInfo.comments());
- if (!sectionComment.isBlank()
- && (!(config instanceof LeafWorldConfig worldConfig) || worldConfig.isDefaultsConfig())) {
+ if (sectionComment != null && (!(config instanceof LeafWorldConfig worldConfig) || worldConfig.isDefaultsConfig())) {
config.addComment(basePath, sectionComment);
}
@@ -72,10 +71,11 @@ private static void bind(
field.setAccessible(true);
Object target = global ? null : module;
+ String path = ConfigPathResolver.fieldPath(moduleClass, field);
Object defaultValue = field.get(target);
String comment = config.pickStringRegionBased(configInfo.comments());
// Always call readValue, to keep comments on reloading
- Object loadedValue = readValue(config, path(basePath, configInfo), comment, field, defaultValue);
+ Object loadedValue = readValue(config, path, comment, field, defaultValue);
if (!skipReload) {
field.set(target, loadedValue);
@@ -96,36 +96,13 @@ private static void validateField(Class> moduleClass, Field field, boolean glo
}
}
- private static String basePath(ConfigClassInfo info) {
- List path = new ArrayList<>();
- path.add(info.category().basePath());
- path.addAll(List.of(info.directory()));
- path.add(info.name());
- return joinPath(path);
- }
-
- private static String path(String basePath, ConfigInfo info) {
- List path = new ArrayList<>();
- path.add(basePath);
- path.addAll(List.of(info.directory()));
- path.add(info.name());
- return joinPath(path);
- }
-
- private static String joinPath(List path) {
- if (path.stream().anyMatch(String::isBlank)) {
- throw new IllegalStateException("Configuration path segments must not be blank: " + path);
- }
- return String.join(".", path);
- }
-
@SuppressWarnings({"unchecked", "rawtypes"})
private static Object readValue(
LeafConfigAccessor config,
String path,
- String comment,
+ @Nullable String comment,
Field field,
- Object defaultValue
+ @Nullable Object defaultValue
) {
if (defaultValue == null) {
throw new IllegalStateException("Configuration field has a null default value: " + field);
@@ -133,37 +110,37 @@ private static Object readValue(
Class> type = field.getType();
if (type == boolean.class || type == Boolean.class) {
- return comment.isBlank()
+ return comment == null
? config.getBoolean(path, (Boolean) defaultValue)
: config.getBoolean(path, (Boolean) defaultValue, comment);
}
if (type == int.class || type == Integer.class) {
- return comment.isBlank()
+ return comment == null
? config.getInt(path, (Integer) defaultValue)
: config.getInt(path, (Integer) defaultValue, comment);
}
if (type == long.class || type == Long.class) {
- return comment.isBlank()
+ return comment == null
? config.getLong(path, (Long) defaultValue)
: config.getLong(path, (Long) defaultValue, comment);
}
if (type == double.class || type == Double.class) {
- return comment.isBlank()
+ return comment == null
? config.getDouble(path, (Double) defaultValue)
: config.getDouble(path, (Double) defaultValue, comment);
}
if (type == String.class) {
- return comment.isBlank()
+ return comment == null
? config.getString(path, (String) defaultValue)
: config.getString(path, (String) defaultValue, comment);
}
if (List.class.isAssignableFrom(type)) {
- return comment.isBlank()
+ return comment == null
? config.getList(path, (List) defaultValue)
: config.getList(path, (List) defaultValue, comment);
}
if (type.isEnum()) {
- String value = comment.isBlank()
+ String value = comment == null
? config.getString(path, ((Enum>) defaultValue).name())
: config.getString(path, ((Enum>) defaultValue).name(), comment);
try {
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModule.java b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModule.java
index 5588c74107..216869d296 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModule.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModule.java
@@ -36,5 +36,4 @@ static void loadAfterBootstrap() {
static void clearModules() {
ConfigModuleLoader.clearModules();
}
-
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModuleLoader.java b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModuleLoader.java
index 7d3f0bfe61..e36fdf5d17 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModuleLoader.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModuleLoader.java
@@ -81,6 +81,7 @@ static void loadAfterBootstrap() {
try {
LeafConfig.globalConfig().saveConfig();
+ LeafConfig.completeGlobalConfigMigration();
} catch (Exception exception) {
LeafConfig.LOGGER.error("Failed to save config file!", exception);
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigPathResolver.java b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigPathResolver.java
new file mode 100644
index 0000000000..4019826d8d
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigPathResolver.java
@@ -0,0 +1,29 @@
+package org.dreeam.leaf.config;
+
+import org.dreeam.leaf.config.annotations.ConfigClassInfo;
+import org.dreeam.leaf.config.annotations.ConfigInfo;
+
+import java.lang.reflect.Field;
+
+/** Resolves annotation-driven module and option paths. */
+public final class ConfigPathResolver {
+
+ public static String modulePath(Class> moduleClass) {
+ ConfigClassInfo info = moduleClass.getAnnotation(ConfigClassInfo.class);
+ if (info == null) {
+ throw new IllegalStateException("Configuration module " + moduleClass.getName()
+ + " is missing @ConfigClassInfo");
+ }
+
+ return info.category().basePath() + '.' + info.name();
+ }
+
+ public static String fieldPath(Class> moduleClass, Field field) {
+ ConfigInfo info = field.getAnnotation(ConfigInfo.class);
+ if (info == null) {
+ throw new IllegalStateException("Configuration field " + field + " is missing @ConfigInfo");
+ }
+
+ return modulePath(moduleClass) + '.' + info.name();
+ }
+}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java
index a9960510fb..4b8b7d6816 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java
@@ -1,5 +1,6 @@
package org.dreeam.leaf.config;
+import io.github.thatsmusic99.configurationmaster.api.ConfigFile;
import io.papermc.paper.SparksFly;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
@@ -7,6 +8,7 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dreeam.leaf.config.modules.misc.SentryDSN;
+import org.dreeam.leaf.config.migration.gale.GaleConfigMigration;
import org.jspecify.annotations.NullMarked;
import org.bukkit.Bukkit;
import org.bukkit.World;
@@ -109,20 +111,36 @@ private static void loadConfig(boolean init) throws Exception {
File globalConfigFile = new File(CONFIG_DIRECTORY, GLOBAL_CONFIG_FILE);
File worldDefaultsFile = new File(CONFIG_DIRECTORY, DEFAULT_WORLD_CONFIG_FILE);
- // Read and migrate existing raw values before either config applies defaults.
- LeafConfigMigration.migrate(globalConfigFile, worldDefaultsFile);
+ if (!worldDefaultsFile.exists()) {
+ Files.createFile(worldDefaultsFile.toPath());
+ }
- globalConfig = new LeafGlobalConfig(init);
+ ConfigFile globalConfigFileData = ConfigFile.loadConfig(globalConfigFile);
+ ConfigFile worldDefaultsFileData = ConfigFile.loadConfig(worldDefaultsFile);
+
+ if (init) {
+ // Migrate the same raw config instances that will be bound and saved below.
+ LeafConfigMigration.migrate(globalConfigFileData, worldDefaultsFileData);
+
+ GaleConfigMigration.migrate(
+ CONFIG_DIRECTORY.toPath(),
+ globalConfigFileData,
+ worldDefaultsFileData
+ );
+ }
+
+ globalConfig = new LeafGlobalConfig(globalConfigFileData);
// Load config modules
ConfigModule.initModules();
- if (!worldDefaultsFile.exists()) {
- Files.createFile(worldDefaultsFile.toPath());
- }
LeafWorldConfig previousWorldDefaults = worldDefaultsConfig;
- worldDefaultsConfig = LeafWorldConfig.loadDefaults(worldDefaultsFile, previousWorldDefaults);
+ worldDefaultsConfig = LeafWorldConfig.loadDefaults(
+ worldDefaultsFileData,
+ previousWorldDefaults
+ );
worldDefaultsConfig.saveConfig();
+ GaleConfigMigration.completeWorldDefaults(worldDefaultsFile.toPath());
ConfigModuleLoader.markInitialized();
}
@@ -139,6 +157,14 @@ public static LeafWorldConfig worldDefaultsConfig() {
*/
public static LeafWorldConfig createWorldConfig(Path worldDirectory) {
File worldConfigFile = worldDirectory.resolve(WORLD_CONFIG_FILE).toFile();
+ LeafWorldConfig migratedConfig = GaleConfigMigration.migrateWorldOverride(
+ worldDirectory,
+ worldConfigFile,
+ worldDefaultsConfig
+ );
+ if (migratedConfig != null) {
+ return migratedConfig;
+ }
if (!LeafWorldConfig.exists(worldConfigFile)) {
return worldDefaultsConfig;
}
@@ -149,6 +175,10 @@ public static LeafWorldConfig createWorldConfig(Path worldDirectory) {
}
}
+ static void completeGlobalConfigMigration() {
+ GaleConfigMigration.completeGlobal(CONFIG_DIRECTORY.toPath().resolve(GLOBAL_CONFIG_FILE));
+ }
+
static boolean isChineseLocale() {
return IS_CHINESE_LOCALE;
}
@@ -350,9 +380,7 @@ public int compareTo(ConfigVersion other) {
private static List buildSparkExtraConfigs() {
List extraConfigs = new ArrayList<>(Arrays.asList(
"config/leaf-global.yml",
- "config/leaf-world-defaults.yml",
- "config/gale-global.yml",
- "config/gale-world-defaults.yml"
+ "config/leaf-world-defaults.yml"
));
String existing = System.getProperty(SPARK_EXTRA_CONFIG_PROPERTY);
@@ -365,21 +393,21 @@ private static List buildSparkExtraConfigs() {
// instead of using SplitYamlConfigParser.INSTANCE for the extra config
// However it's better to choose bundled spark for better view.
for (World world : Bukkit.getWorlds()) {
- Path galeWorldFolder = world.getWorldFolder().toPath().resolve("gale-world.yml");
- extraConfigs.add(galeWorldFolder.toString().replace("\\", "/").replace("./", "")); // Gale world config
Path leafWorldFile = world.getWorldFolder().toPath().resolve(WORLD_CONFIG_FILE);
- if (Files.isRegularFile(leafWorldFile)) {
- extraConfigs.add(leafWorldFile.toString().replace("\\", "/").replace("./", ""));
- }
+ extraConfigs.add(leafWorldFile.toString().replace("\\", "/").replace("./", "")); // Leaf world override config
}
return extraConfigs;
}
private static List buildSparkHiddenPaths() {
+ List extraHidden = new ArrayList<>();
+
String existing = System.getProperty(SPARK_HIDDEN_PATHS_PROPERTY);
+ if (existing != null) {
+ extraHidden.addAll(Arrays.asList(existing.split(",")));
+ }
- List extraHidden = existing != null ? new ArrayList<>(Arrays.asList(existing.split(","))) : new ArrayList<>();
extraHidden.add(SentryDSN.sentryDsnConfigPath); // Hide Sentry DSN key
return extraHidden;
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigAccessor.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigAccessor.java
index e1d6091680..5873cf2267 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigAccessor.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigAccessor.java
@@ -2,6 +2,7 @@
import io.github.thatsmusic99.configurationmaster.api.ConfigFile;
import io.github.thatsmusic99.configurationmaster.api.ConfigSection;
+import org.jspecify.annotations.Nullable;
import java.io.File;
import java.util.List;
@@ -13,7 +14,11 @@ abstract class LeafConfigAccessor {
protected final ConfigFile configFile;
protected LeafConfigAccessor(File file) throws Exception {
- this.configFile = ConfigFile.loadConfig(file);
+ this(ConfigFile.loadConfig(file));
+ }
+
+ protected LeafConfigAccessor(ConfigFile configFile) {
+ this.configFile = configFile;
}
public void saveConfig() throws Exception {
@@ -171,7 +176,9 @@ public void addCommentRegionBased(String path, String en, String cn) {
configFile.addComment(path, LeafConfig.isChineseLocale() ? cn : en);
}
- public String pickStringRegionBased(String[] localizedStrings) {
+ public @Nullable String pickStringRegionBased(String... localizedStrings) {
+ if (localizedStrings == null || localizedStrings.length == 0) return null;
+ if (localizedStrings.length == 1) return localizedStrings[0];
return LeafConfig.isChineseLocale() ? localizedStrings[1] : localizedStrings[0];
}
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigMigration.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigMigration.java
index 8cfe3ea93a..9ae3c8be6a 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigMigration.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigMigration.java
@@ -3,29 +3,21 @@
import io.github.thatsmusic99.configurationmaster.api.ConfigFile;
import io.github.thatsmusic99.configurationmaster.api.ConfigSection;
-import java.io.File;
import java.util.Objects;
/**
- * Applies versioned migrations to raw Leaf configuration files before defaults are added.
+ * Applies versioned migrations to the loaded Leaf configuration instances before defaults are added.
*/
final class LeafConfigMigration {
private LeafConfigMigration() {
}
- static void migrate(File globalFile, File worldDefaultsFile) throws Exception {
- if (!globalFile.isFile()) {
- return;
- }
-
- ConfigFile globalConfig = ConfigFile.loadConfig(globalFile);
+ static void migrate(ConfigFile globalConfig, ConfigFile worldDefaultsConfig) throws Exception {
String storedVersion = globalConfig.getString("config-version", null);
- MigrationContext context = new MigrationContext(globalConfig, worldDefaultsFile);
+ MigrationContext context = new MigrationContext(globalConfig, worldDefaultsConfig);
applyMigrations(storedVersion, context);
-
- context.saveChanges();
}
private static void applyMigrations(String storedVersion, MigrationContext context) throws Exception {
@@ -49,14 +41,11 @@ private enum ConfigFileType {
private static final class MigrationContext {
private final ConfigFile globalConfig;
- private final File worldDefaultsFile;
- private ConfigFile worldDefaultsConfig;
- private boolean globalChanged;
- private boolean worldDefaultsChanged;
+ private final ConfigFile worldDefaultsConfig;
- private MigrationContext(ConfigFile globalConfig, File worldDefaultsFile) {
+ private MigrationContext(ConfigFile globalConfig, ConfigFile worldDefaultsConfig) {
this.globalConfig = globalConfig;
- this.worldDefaultsFile = worldDefaultsFile;
+ this.worldDefaultsConfig = worldDefaultsConfig;
}
private void migrate(
@@ -67,8 +56,8 @@ private void migrate(
) throws Exception {
validateMigration(source, oldPath, target, newPath);
- ConfigFile sourceConfig = config(source, false);
- if (sourceConfig == null || !sourceConfig.contains(oldPath)) {
+ ConfigFile sourceConfig = config(source);
+ if (!sourceConfig.contains(oldPath)) {
return;
}
@@ -78,15 +67,12 @@ private void migrate(
+ source + ":" + oldPath);
}
- ConfigFile targetConfig = config(target, true);
+ ConfigFile targetConfig = config(target);
if (targetConfig.contains(newPath)) {
sourceConfig.set(oldPath, null);
} else {
sourceConfig.moveTo(oldPath, newPath, targetConfig);
}
-
- markChanged(source);
- markChanged(target);
}
private static void validateMigration(
@@ -116,35 +102,8 @@ private static void requirePath(String path, String name) {
}
}
- private ConfigFile config(ConfigFileType type, boolean create) throws Exception {
- if (type == ConfigFileType.GLOBAL) {
- return this.globalConfig;
- }
- if (this.worldDefaultsConfig != null) {
- return this.worldDefaultsConfig;
- }
- if (!create && !this.worldDefaultsFile.isFile()) {
- return null;
- }
- this.worldDefaultsConfig = ConfigFile.loadConfig(this.worldDefaultsFile);
- return this.worldDefaultsConfig;
- }
-
- private void markChanged(ConfigFileType type) {
- if (type == ConfigFileType.GLOBAL) {
- this.globalChanged = true;
- } else {
- this.worldDefaultsChanged = true;
- }
- }
-
- private void saveChanges() throws Exception {
- if (this.globalChanged) {
- this.globalConfig.save();
- }
- if (this.worldDefaultsChanged) {
- this.worldDefaultsConfig.save();
- }
+ private ConfigFile config(ConfigFileType type) {
+ return type == ConfigFileType.GLOBAL ? this.globalConfig : this.worldDefaultsConfig;
}
}
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafGlobalConfig.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafGlobalConfig.java
index b242f32498..91017746ad 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafGlobalConfig.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafGlobalConfig.java
@@ -1,24 +1,21 @@
package org.dreeam.leaf.config;
-import java.io.File;
+import io.github.thatsmusic99.configurationmaster.api.ConfigFile;
+
+import java.util.Objects;
/** The server-wide Leaf configuration. */
public final class LeafGlobalConfig extends LeafConfigAccessor {
- public LeafGlobalConfig(boolean init) throws Exception {
- this(new File(LeafConfig.CONFIG_DIRECTORY, LeafConfig.GLOBAL_CONFIG_FILE), true);
- }
+ LeafGlobalConfig(ConfigFile configFile) {
+ super(configFile);
- LeafGlobalConfig(File file, boolean loadConfigVersion) throws Exception {
- super(file);
+ LeafConfig.loadPreviousConfigVersion(getString("config-version"));
- if (loadConfigVersion) {
- LeafConfig.loadPreviousConfigVersion(getString("config-version"));
- }
configFile.set("config-version", LeafConfig.CURRENT_CONFIG_VERSION);
- configFile.addComments("config-version", pickStringRegionBased("""
+ configFile.addComments("config-version", Objects.requireNonNull(pickStringRegionBased("""
Leaf Config
Website: https://www.leafmc.one/
@@ -31,7 +28,7 @@ public LeafGlobalConfig(boolean init) throws Exception {
官网: https://www.leafmc.one/zh/
文档: https://www.leafmc.one/zh/docs/getting-started
GitHub 仓库: https://github.com/Winds-Studio/Leaf
- QQ社区群: 619278377"""));
+ QQ社区群: 619278377""")));
structureConfig();
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafWorldConfig.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafWorldConfig.java
index 094dfab9c1..b2e58a0d1f 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafWorldConfig.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafWorldConfig.java
@@ -1,6 +1,8 @@
package org.dreeam.leaf.config;
+import io.github.thatsmusic99.configurationmaster.api.ConfigFile;
import io.github.thatsmusic99.configurationmaster.api.ConfigSection;
+import org.jspecify.annotations.Nullable;
import java.io.File;
import java.util.LinkedHashMap;
@@ -15,7 +17,7 @@
*/
public final class LeafWorldConfig extends LeafConfigAccessor {
- private final LeafWorldConfig defaults;
+ private final @Nullable LeafWorldConfig defaults;
private LeafWorldConfig reloadSource;
private final Map, WorldConfigModule> modules = new LinkedHashMap<>();
public boolean secureSeedEnabled;
@@ -28,14 +30,47 @@ static LeafWorldConfig loadDefaults(File file, LeafWorldConfig reloadSource) thr
return new LeafWorldConfig(file, null, reloadSource);
}
+ static LeafWorldConfig loadDefaults(
+ ConfigFile configFile,
+ LeafWorldConfig reloadSource
+ ) {
+ return new LeafWorldConfig(configFile, null, reloadSource);
+ }
+
public LeafWorldConfig(File file, LeafWorldConfig defaults) throws Exception {
this(file, defaults, null);
}
- private LeafWorldConfig(File file, LeafWorldConfig defaults, LeafWorldConfig reloadSource) throws Exception {
+ public static LeafWorldConfig loadOverride(
+ ConfigFile configFile,
+ LeafWorldConfig defaults
+ ) {
+ return new LeafWorldConfig(configFile, defaults, null);
+ }
+
+ private LeafWorldConfig(
+ File file,
+ @Nullable LeafWorldConfig defaults,
+ LeafWorldConfig reloadSource
+ ) throws Exception {
super(file);
this.defaults = defaults;
this.reloadSource = reloadSource;
+ loadModules();
+ }
+
+ private LeafWorldConfig(
+ ConfigFile configFile,
+ @Nullable LeafWorldConfig defaults,
+ LeafWorldConfig reloadSource
+ ) {
+ super(configFile);
+ this.defaults = defaults;
+ this.reloadSource = reloadSource;
+ loadModules();
+ }
+
+ private void loadModules() {
try {
ConfigModuleLoader.loadWorldModules(this);
} finally {
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/ConfigClassInfo.java b/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/ConfigClassInfo.java
index b59a2b9938..ee974fff38 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/ConfigClassInfo.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/ConfigClassInfo.java
@@ -17,7 +17,10 @@
String name();
- String[] directory() default {};
-
+ /**
+ * Optional section comment. Text blocks are supported.
+ *
+ * Supply one value for all locales, or English and Chinese values in that order.
+ */
String[] comments() default {};
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/ConfigInfo.java b/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/ConfigInfo.java
index 022ce8996d..6d57a9d118 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/ConfigInfo.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/ConfigInfo.java
@@ -13,7 +13,10 @@
String name();
- String[] directory() default {};
-
+ /**
+ * Optional option comment. Text blocks are supported.
+ *
+ * Supply one value for all locales, or English and Chinese values in that order.
+ */
String[] comments() default {};
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/migration/gale/GaleConfigMigration.java b/leaf-server/src/main/java/org/dreeam/leaf/config/migration/gale/GaleConfigMigration.java
new file mode 100644
index 0000000000..fdb99ec922
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/migration/gale/GaleConfigMigration.java
@@ -0,0 +1,464 @@
+package org.dreeam.leaf.config.migration.gale;
+
+import io.github.thatsmusic99.configurationmaster.api.ConfigFile;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.dreeam.leaf.config.ConfigModule;
+import org.dreeam.leaf.config.ConfigPathResolver;
+import org.dreeam.leaf.config.LeafWorldConfig;
+import org.dreeam.leaf.config.WorldConfigModule;
+import org.dreeam.leaf.config.annotations.ConfigInfo;
+import org.dreeam.leaf.config.modules.gameplay.BookWriting;
+import org.dreeam.leaf.config.modules.opt.SaveFireworks;
+import org.jspecify.annotations.Nullable;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/** Migrates the removed Gale configuration files into Leaf configuration modules. */
+public final class GaleConfigMigration {
+
+ private static final Logger LOGGER = LogManager.getLogger(GaleConfigMigration.class.getSimpleName());
+ private static final DateTimeFormatter BACKUP_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss");
+ private static final String GLOBAL_FILE = "gale-global.yml";
+ private static final String WORLD_DEFAULTS_FILE = "gale-world-defaults.yml";
+ private static final String WORLD_OVERRIDE_FILE = "gale-world.yml";
+ private static final Set IGNORED_PATHS = Set.of("_version");
+
+ private static Path configDirectory;
+ private static List globalMappings = List.of();
+ private static List worldMappings = List.of();
+ private static Map resolvedWorldMappings = Map.of();
+ private static PendingMigration globalMigration;
+ private static PendingMigration worldDefaultsMigration;
+ private static Path backupDirectory;
+ private static boolean migrationEnabled;
+
+ private GaleConfigMigration() {
+ }
+
+ public static void migrate(Path directory, ConfigFile leafGlobalConfig, ConfigFile leafWorldDefaultsConfig) {
+ configDirectory = Objects.requireNonNull(directory, "directory").normalize();
+ backupDirectory = null;
+ globalMigration = null;
+ worldDefaultsMigration = null;
+
+ Path globalPath = configDirectory.resolve(GLOBAL_FILE);
+ Path worldDefaultsPath = configDirectory.resolve(WORLD_DEFAULTS_FILE);
+ migrationEnabled = Files.isRegularFile(globalPath) || Files.isRegularFile(worldDefaultsPath);
+ if (!migrationEnabled) {
+ globalMappings = List.of();
+ worldMappings = List.of();
+ resolvedWorldMappings = Map.of();
+ return;
+ }
+
+ registerMappings();
+ Map resolvedGlobalMappings = resolveMappings(globalMappings, true);
+ resolvedWorldMappings = resolveMappings(worldMappings, false);
+ globalMigration = collectMigration(globalPath, Path.of(""), resolvedGlobalMappings);
+ worldDefaultsMigration = collectMigration(worldDefaultsPath, Path.of(""), resolvedWorldMappings);
+ migrateValues(globalMigration, leafGlobalConfig);
+ migrateValues(worldDefaultsMigration, leafWorldDefaultsConfig);
+ }
+
+ private static void registerMappings() {
+ globalMappings = new ArrayList<>();
+ worldMappings = new ArrayList<>();
+
+ addGlobalMapping("gameplay-mechanics.enable-book-writing", BookWriting.class, "enabled");
+
+ addWorldMapping("small-optimizations.save-fireworks", SaveFireworks.class, "enabled");
+
+ globalMappings = List.copyOf(globalMappings);
+ worldMappings = List.copyOf(worldMappings);
+ }
+
+ private static void addGlobalMapping(
+ String oldPath,
+ Class> moduleClass,
+ String fieldName
+ ) {
+ globalMappings.add(new Mapping(oldPath, moduleClass, fieldName));
+ }
+
+ private static void addWorldMapping(
+ String oldPath,
+ Class> moduleClass,
+ String fieldName
+ ) {
+ worldMappings.add(new Mapping(oldPath, moduleClass, fieldName));
+ }
+
+ public static void completeGlobal(Path leafFile) {
+ complete(globalMigration, leafFile);
+ globalMigration = null;
+ }
+
+ public static void completeWorldDefaults(Path leafFile) {
+ complete(worldDefaultsMigration, leafFile);
+ worldDefaultsMigration = null;
+ }
+
+ /**
+ * Attempts to create a Leaf override from Gale values.
+ *
+ * @return the newly created Leaf override, or {@code null} when normal Leaf loading should continue
+ */
+ public static @Nullable LeafWorldConfig migrateWorldOverride(
+ Path worldDirectory,
+ File leafFile,
+ LeafWorldConfig defaults
+ ) {
+ if (!migrationEnabled) {
+ return null;
+ }
+
+ Path leafPath = leafFile.toPath();
+ Path galePath = worldDirectory.resolve(WORLD_OVERRIDE_FILE);
+
+ if (LeafWorldConfig.exists(leafFile)) {
+ if (Files.isRegularFile(galePath)) {
+ renameOverride(galePath, "A Leaf world override already exists for " + worldDirectory + '.');
+ }
+ return null;
+ }
+ if (!Files.isRegularFile(galePath)) {
+ return null;
+ }
+
+ PendingMigration migration = collectMigration(
+ galePath,
+ worldContext(worldDirectory),
+ resolvedWorldMappings
+ );
+ if (migration == null) {
+ return null;
+ }
+ if (!migration.hasValues()) {
+ archive(migration);
+ return null;
+ }
+ if (!migration.unmappedPaths().isEmpty()) {
+ LOGGER.warn(
+ "Gale world config {} has option path(s) without Leaf mappings: {}. Deferring the whole override migration.",
+ galePath,
+ migration.unmappedPaths()
+ );
+ return null;
+ }
+ if (!migration.invalidPaths().isEmpty()) {
+ renameOverride(galePath, "The Gale world override could not be converted.");
+ return null;
+ }
+
+ boolean leafFileCreated = false;
+ try {
+ Files.createFile(leafPath);
+ leafFileCreated = true;
+ ConfigFile leafConfig = ConfigFile.loadConfig(leafFile);
+ migrateValues(migration, leafConfig);
+ LeafWorldConfig migrated = LeafWorldConfig.loadOverride(leafConfig, defaults);
+ migrated.saveConfig();
+ if (!containsAll(leafPath, migration.values().keySet())) {
+ throw new IllegalStateException("Not all Gale world values were written to " + leafPath);
+ }
+
+ archive(migration);
+ return migrated;
+ } catch (Exception exception) {
+ if (leafFileCreated) {
+ try {
+ Files.deleteIfExists(leafPath);
+ } catch (IOException cleanupException) {
+ exception.addSuppressed(cleanupException);
+ }
+ }
+ LOGGER.error("Failed to migrate Gale world config for {}; using Leaf world defaults.", worldDirectory, exception);
+ renameOverride(galePath, "The Leaf world override could not be saved.");
+ return null;
+ }
+ }
+
+ private static void migrateValues(@Nullable PendingMigration migration, ConfigFile leafConfig) {
+ if (migration == null) {
+ return;
+ }
+ migration.values().forEach(leafConfig::set);
+ }
+
+ private static void complete(@Nullable PendingMigration migration, Path leafFile) {
+ if (migration == null) {
+ return;
+ }
+ List remainingPaths = new ArrayList<>(migration.unmappedPaths());
+ remainingPaths.addAll(migration.invalidPaths());
+ if (!remainingPaths.isEmpty()) {
+ LOGGER.warn("Gale config {} still has unmigrated option path(s): {}. Leaving it in place.",
+ migration.sourcePath(), remainingPaths);
+ return;
+ }
+ try {
+ if (!containsAll(leafFile, migration.values().keySet())) {
+ LOGGER.error("Gale values were not all written to {}; leaving {} in place.",
+ leafFile, migration.sourcePath());
+ return;
+ }
+ } catch (Exception exception) {
+ LOGGER.error("Failed to verify migrated Leaf config {}; leaving {} in place.",
+ leafFile, migration.sourcePath(), exception);
+ return;
+ }
+ archive(migration);
+ }
+
+ private static boolean containsAll(Path leafFile, Set paths) throws Exception {
+ if (paths.isEmpty()) {
+ return true;
+ }
+ ConfigFile config = ConfigFile.loadConfig(leafFile.toFile());
+ return paths.stream().allMatch(config::contains);
+ }
+
+ private static Map resolveMappings(
+ List mappings,
+ boolean global
+ ) {
+ Map resolvedMappings = new LinkedHashMap<>();
+ for (Mapping mapping : mappings) {
+ String oldPath = mapping.oldPath();
+ if (oldPath.isBlank()) {
+ throw new IllegalArgumentException("Gale config path must not be blank");
+ }
+
+ Field field;
+ try {
+ field = mapping.moduleClass().getDeclaredField(mapping.fieldName());
+ } catch (NoSuchFieldException exception) {
+ throw new IllegalArgumentException("Invalid Gale migration target "
+ + mapping.moduleClass().getName() + '.' + mapping.fieldName(), exception);
+ }
+ validateTarget(field, global);
+
+ String leafPath = ConfigPathResolver.fieldPath(mapping.moduleClass(), field);
+
+ resolvedMappings.put(oldPath, new ResolvedTarget(field, leafPath));
+ }
+ return Map.copyOf(resolvedMappings);
+ }
+
+ private static void validateTarget(Field field, boolean global) {
+ Class> expectedModuleType = global ? ConfigModule.class : WorldConfigModule.class;
+ if (!expectedModuleType.isAssignableFrom(field.getDeclaringClass())
+ || !field.isAnnotationPresent(ConfigInfo.class)
+ || Modifier.isStatic(field.getModifiers()) != global
+ || Modifier.isFinal(field.getModifiers())) {
+ throw new IllegalArgumentException("Invalid Gale migration target: " + field);
+ }
+ }
+
+ private static Path worldContext(Path worldDirectory) {
+ Path absolute = worldDirectory.toAbsolutePath().normalize();
+ Path workingDirectory = Path.of("").toAbsolutePath().normalize();
+ return absolute.startsWith(workingDirectory)
+ ? workingDirectory.relativize(absolute)
+ : absolute.subpath(0, absolute.getNameCount());
+ }
+
+ private static void renameOverride(Path source, String reason) {
+ Path target = uniqueOldPath(source);
+ try {
+ Files.move(source, target);
+ LOGGER.warn("{} Renamed Gale config from {} to {}.", reason, source, target);
+ } catch (IOException exception) {
+ LOGGER.error("Failed to rename Gale config {}; leaving it in place.", source, exception);
+ }
+ }
+
+ private static Path uniqueOldPath(Path source) {
+ Path target = source.resolveSibling(source.getFileName() + "_old");
+ if (!Files.exists(target)) {
+ return target;
+ }
+ String suffix = BACKUP_TIME_FORMAT.format(LocalDateTime.now());
+ for (int counter = 0; ; counter++) {
+ Path candidate = source.resolveSibling(source.getFileName() + "_old-" + suffix
+ + (counter == 0 ? "" : '-' + Integer.toString(counter)));
+ if (!Files.exists(candidate)) {
+ return candidate;
+ }
+ }
+ }
+
+ private static synchronized Path backupDirectory() throws IOException {
+ if (backupDirectory != null) {
+ return backupDirectory;
+ }
+ Path root = configDirectory.resolve("backup");
+ Files.createDirectories(root);
+ String name = "backup-" + BACKUP_TIME_FORMAT.format(LocalDateTime.now());
+ for (int counter = 0; ; counter++) {
+ Path candidate = root.resolve(counter == 0 ? name : name + '-' + counter);
+ try {
+ Files.createDirectory(candidate);
+ backupDirectory = candidate;
+ return candidate;
+ } catch (FileAlreadyExistsException ignored) {
+ }
+ }
+ }
+
+ private static @Nullable PendingMigration collectMigration(
+ Path sourcePath,
+ Path worldContext,
+ Map mappings
+ ) {
+ if (!Files.isRegularFile(sourcePath)) {
+ return null;
+ }
+ try {
+ ConfigFile config = ConfigFile.loadConfig(sourcePath.toFile());
+ List valuePaths = new ArrayList<>();
+ collectValuePaths(config, "", valuePaths);
+ valuePaths.removeAll(IGNORED_PATHS);
+
+ List unmappedPaths = valuePaths.stream()
+ .filter(path -> !mappings.containsKey(path))
+ .toList();
+ Map values = new LinkedHashMap<>();
+ List invalidPaths = new ArrayList<>();
+ for (Map.Entry entry : mappings.entrySet()) {
+ String oldPath = entry.getKey();
+ if (!config.contains(oldPath)) {
+ continue;
+ }
+ Object oldValue = config.get(oldPath);
+ if (oldValue == null || oldValue instanceof Map, ?>) {
+ LOGGER.warn("Gale path '{}' in {} is not an option; leaving it unmigrated.",
+ oldPath, sourcePath);
+ invalidPaths.add(oldPath);
+ continue;
+ }
+ try {
+ ResolvedTarget target = entry.getValue();
+ values.put(target.leafPath(), convertValue(oldValue, target.field()));
+ } catch (IllegalArgumentException exception) {
+ LOGGER.warn("Gale path '{}' in {} is invalid for {}; leaving it unmigrated.",
+ oldPath, sourcePath, entry.getValue().field(), exception);
+ invalidPaths.add(oldPath);
+ }
+ }
+
+ Path backupPath = worldContext.toString().isEmpty()
+ ? Path.of(sourcePath.getFileName().toString())
+ : Path.of("world-overrides").resolve(worldContext).resolve(sourcePath.getFileName().toString());
+ return new PendingMigration(
+ sourcePath,
+ backupPath,
+ Map.copyOf(values),
+ List.copyOf(unmappedPaths),
+ List.copyOf(invalidPaths),
+ !valuePaths.isEmpty()
+ );
+ } catch (Exception exception) {
+ LOGGER.error("Failed to read Gale config {}; migration was skipped.", sourcePath, exception);
+ return null;
+ }
+ }
+
+ private static void archive(PendingMigration migration) {
+ try {
+ Path relative = migration.backupPath().normalize();
+ if (relative.isAbsolute() || relative.startsWith("..")) {
+ throw new IOException("Invalid Gale backup path: " + relative);
+ }
+ Path target = backupDirectory().resolve(relative).normalize();
+ Files.createDirectories(target.getParent());
+ Files.move(migration.sourcePath(), target);
+ LOGGER.warn("Moved migrated Gale config {} to {}.", migration.sourcePath(), target);
+ } catch (IOException exception) {
+ LOGGER.error("Failed to back up Gale config {}; leaving it in place.",
+ migration.sourcePath(), exception);
+ }
+ }
+
+ private static void collectValuePaths(Map, ?> values, String parent, List paths) {
+ for (Map.Entry, ?> entry : values.entrySet()) {
+ String name = String.valueOf(entry.getKey());
+ String path = parent.isEmpty() ? name : parent + '.' + name;
+ Object value = entry.getValue();
+ if (value instanceof Map, ?> nested) {
+ collectValuePaths(nested, path, paths);
+ } else if (value != null) {
+ paths.add(path);
+ }
+ }
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ private static Object convertValue(Object value, Field field) {
+ Class> type = field.getType();
+ if (type == boolean.class || type == Boolean.class) {
+ if (value instanceof Boolean booleanValue) {
+ return booleanValue;
+ }
+ String booleanValue = String.valueOf(value);
+ if (booleanValue.equalsIgnoreCase("true")) {
+ return true;
+ }
+ if (booleanValue.equalsIgnoreCase("false")) {
+ return false;
+ }
+ throw new IllegalArgumentException("Not a boolean: " + value);
+ }
+ if (type == int.class || type == Integer.class) {
+ return value instanceof Number number ? number.intValue() : Integer.parseInt(String.valueOf(value));
+ }
+ if (type == long.class || type == Long.class) {
+ return value instanceof Number number ? number.longValue() : Long.parseLong(String.valueOf(value));
+ }
+ if (type == double.class || type == Double.class) {
+ return value instanceof Number number ? number.doubleValue() : Double.parseDouble(String.valueOf(value));
+ }
+ if (type == String.class) {
+ return String.valueOf(value);
+ }
+ if (List.class.isAssignableFrom(type) && value instanceof List> list) {
+ return list.stream().map(String::valueOf).toList();
+ }
+ if (type.isEnum()) {
+ return Enum.valueOf((Class extends Enum>) type, String.valueOf(value).toUpperCase(Locale.ROOT));
+ }
+ throw new IllegalArgumentException("Unsupported migrated config field type: " + field);
+ }
+
+ private record ResolvedTarget(Field field, String leafPath) {
+ }
+
+ private record Mapping(String oldPath, Class> moduleClass, String fieldName) {
+ }
+
+ private record PendingMigration(
+ Path sourcePath,
+ Path backupPath,
+ Map values,
+ List unmappedPaths,
+ List invalidPaths,
+ boolean hasValues
+ ) {
+ }
+}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/package-info.java b/leaf-server/src/main/java/org/dreeam/leaf/config/package-info.java
new file mode 100644
index 0000000000..7ed863b702
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/package-info.java
@@ -0,0 +1,2 @@
+@org.jspecify.annotations.NullMarked
+package org.dreeam.leaf.config;
From 19e7457b1e7a5bdca31dc69c17440cba947f865d Mon Sep 17 00:00:00 2001
From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com>
Date: Fri, 14 Aug 2026 08:14:07 +0800
Subject: [PATCH 07/14] Cleanup
Cleanup arch, remove useless logic
---
.../org/dreeam/leaf/config/ConfigBinder.java | 5 +-
.../org/dreeam/leaf/config/ConfigModule.java | 2 +-
.../leaf/config/ConfigModuleLoader.java | 4 +-
.../org/dreeam/leaf/config/LeafConfig.java | 7 +-
.../leaf/config/LeafConfigAccessor.java | 1 +
.../{ => migration}/ConfigPathMigration.java | 2 +-
.../{ => migration}/LeafConfigMigration.java | 6 +-
.../migration/gale/GaleConfigMigration.java | 337 +++++-------------
.../modules/opt/DynamicActivationofBrain.java | 2 +-
.../ConfigPaths.java} | 4 +-
10 files changed, 108 insertions(+), 262 deletions(-)
rename leaf-server/src/main/java/org/dreeam/leaf/config/{ => migration}/ConfigPathMigration.java (96%)
rename leaf-server/src/main/java/org/dreeam/leaf/config/{ => migration}/LeafConfigMigration.java (95%)
rename leaf-server/src/main/java/org/dreeam/leaf/config/{ConfigPathResolver.java => util/ConfigPaths.java} (92%)
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigBinder.java b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigBinder.java
index 1dcf926b38..bac2970552 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigBinder.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigBinder.java
@@ -4,6 +4,7 @@
import org.dreeam.leaf.config.annotations.ConfigInfo;
import org.dreeam.leaf.config.annotations.DoNotLoad;
import org.dreeam.leaf.config.annotations.HotReloadUnsupported;
+import org.dreeam.leaf.config.util.ConfigPaths;
import org.jspecify.annotations.Nullable;
import java.lang.reflect.Field;
@@ -48,7 +49,7 @@ private static void bind(
+ " is missing @ConfigClassInfo");
}
- String basePath = ConfigPathResolver.modulePath(moduleClass);
+ String basePath = ConfigPaths.modulePath(moduleClass);
String sectionComment = config.pickStringRegionBased(classInfo.comments());
if (sectionComment != null && (!(config instanceof LeafWorldConfig worldConfig) || worldConfig.isDefaultsConfig())) {
config.addComment(basePath, sectionComment);
@@ -71,7 +72,7 @@ private static void bind(
field.setAccessible(true);
Object target = global ? null : module;
- String path = ConfigPathResolver.fieldPath(moduleClass, field);
+ String path = ConfigPaths.fieldPath(moduleClass, field);
Object defaultValue = field.get(target);
String comment = config.pickStringRegionBased(configInfo.comments());
// Always call readValue, to keep comments on reloading
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModule.java b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModule.java
index 216869d296..8c59760964 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModule.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModule.java
@@ -21,7 +21,7 @@ default void onLoaded() {
/**
* Runs after this module's configuration fields have been loaded and core registries are available.
*/
- default void onPostLoaded() {
+ default void onRegistriesLoaded() {
}
static void initModules()
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModuleLoader.java b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModuleLoader.java
index e36fdf5d17..16e95606df 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModuleLoader.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModuleLoader.java
@@ -76,12 +76,12 @@ static void initModules()
static void loadAfterBootstrap() {
for (ConfigModule module : LOADED_MODULES) {
- module.onPostLoaded();
+ module.onRegistriesLoaded();
}
try {
LeafConfig.globalConfig().saveConfig();
- LeafConfig.completeGlobalConfigMigration();
+ LeafConfig.finalizeGlobalConfigMigration();
} catch (Exception exception) {
LeafConfig.LOGGER.error("Failed to save config file!", exception);
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java
index 4b8b7d6816..442b3ae172 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java
@@ -7,6 +7,7 @@
import net.minecraft.util.Util;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import org.dreeam.leaf.config.migration.LeafConfigMigration;
import org.dreeam.leaf.config.modules.misc.SentryDSN;
import org.dreeam.leaf.config.migration.gale.GaleConfigMigration;
import org.jspecify.annotations.NullMarked;
@@ -140,7 +141,7 @@ private static void loadConfig(boolean init) throws Exception {
previousWorldDefaults
);
worldDefaultsConfig.saveConfig();
- GaleConfigMigration.completeWorldDefaults(worldDefaultsFile.toPath());
+ GaleConfigMigration.finalizeWorldDefaultsMigration();
ConfigModuleLoader.markInitialized();
}
@@ -175,8 +176,8 @@ public static LeafWorldConfig createWorldConfig(Path worldDirectory) {
}
}
- static void completeGlobalConfigMigration() {
- GaleConfigMigration.completeGlobal(CONFIG_DIRECTORY.toPath().resolve(GLOBAL_CONFIG_FILE));
+ static void finalizeGlobalConfigMigration() {
+ GaleConfigMigration.finalizeGlobalMigration();
}
static boolean isChineseLocale() {
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigAccessor.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigAccessor.java
index 5873cf2267..e6a1ee17cc 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigAccessor.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigAccessor.java
@@ -2,6 +2,7 @@
import io.github.thatsmusic99.configurationmaster.api.ConfigFile;
import io.github.thatsmusic99.configurationmaster.api.ConfigSection;
+import org.dreeam.leaf.config.migration.ConfigPathMigration;
import org.jspecify.annotations.Nullable;
import java.io.File;
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigPathMigration.java b/leaf-server/src/main/java/org/dreeam/leaf/config/migration/ConfigPathMigration.java
similarity index 96%
rename from leaf-server/src/main/java/org/dreeam/leaf/config/ConfigPathMigration.java
rename to leaf-server/src/main/java/org/dreeam/leaf/config/migration/ConfigPathMigration.java
index 6d79fe9ece..88da7f20cc 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigPathMigration.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/migration/ConfigPathMigration.java
@@ -1,4 +1,4 @@
-package org.dreeam.leaf.config;
+package org.dreeam.leaf.config.migration;
import io.github.thatsmusic99.configurationmaster.api.ConfigSection;
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigMigration.java b/leaf-server/src/main/java/org/dreeam/leaf/config/migration/LeafConfigMigration.java
similarity index 95%
rename from leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigMigration.java
rename to leaf-server/src/main/java/org/dreeam/leaf/config/migration/LeafConfigMigration.java
index 9ae3c8be6a..4e34ea40c4 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigMigration.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/migration/LeafConfigMigration.java
@@ -1,4 +1,4 @@
-package org.dreeam.leaf.config;
+package org.dreeam.leaf.config.migration;
import io.github.thatsmusic99.configurationmaster.api.ConfigFile;
import io.github.thatsmusic99.configurationmaster.api.ConfigSection;
@@ -8,12 +8,12 @@
/**
* Applies versioned migrations to the loaded Leaf configuration instances before defaults are added.
*/
-final class LeafConfigMigration {
+public final class LeafConfigMigration {
private LeafConfigMigration() {
}
- static void migrate(ConfigFile globalConfig, ConfigFile worldDefaultsConfig) throws Exception {
+ public static void migrate(ConfigFile globalConfig, ConfigFile worldDefaultsConfig) throws Exception {
String storedVersion = globalConfig.getString("config-version", null);
MigrationContext context = new MigrationContext(globalConfig, worldDefaultsConfig);
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/migration/gale/GaleConfigMigration.java b/leaf-server/src/main/java/org/dreeam/leaf/config/migration/gale/GaleConfigMigration.java
index fdb99ec922..f781adb583 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/migration/gale/GaleConfigMigration.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/migration/gale/GaleConfigMigration.java
@@ -4,12 +4,12 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dreeam.leaf.config.ConfigModule;
-import org.dreeam.leaf.config.ConfigPathResolver;
import org.dreeam.leaf.config.LeafWorldConfig;
import org.dreeam.leaf.config.WorldConfigModule;
import org.dreeam.leaf.config.annotations.ConfigInfo;
import org.dreeam.leaf.config.modules.gameplay.BookWriting;
import org.dreeam.leaf.config.modules.opt.SaveFireworks;
+import org.dreeam.leaf.config.util.ConfigPaths;
import org.jspecify.annotations.Nullable;
import java.io.File;
@@ -24,12 +24,13 @@
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
-/** Migrates the removed Gale configuration files into Leaf configuration modules. */
+/**
+ * Migrates the removed Gale configuration files into Leaf configuration modules.
+ */
public final class GaleConfigMigration {
private static final Logger LOGGER = LogManager.getLogger(GaleConfigMigration.class.getSimpleName());
@@ -42,14 +43,10 @@ public final class GaleConfigMigration {
private static Path configDirectory;
private static List globalMappings = List.of();
private static List worldMappings = List.of();
- private static Map resolvedWorldMappings = Map.of();
+ private static Map resolvedWorldMappings = Map.of();
private static PendingMigration globalMigration;
private static PendingMigration worldDefaultsMigration;
private static Path backupDirectory;
- private static boolean migrationEnabled;
-
- private GaleConfigMigration() {
- }
public static void migrate(Path directory, ConfigFile leafGlobalConfig, ConfigFile leafWorldDefaultsConfig) {
configDirectory = Objects.requireNonNull(directory, "directory").normalize();
@@ -59,21 +56,16 @@ public static void migrate(Path directory, ConfigFile leafGlobalConfig, ConfigFi
Path globalPath = configDirectory.resolve(GLOBAL_FILE);
Path worldDefaultsPath = configDirectory.resolve(WORLD_DEFAULTS_FILE);
- migrationEnabled = Files.isRegularFile(globalPath) || Files.isRegularFile(worldDefaultsPath);
- if (!migrationEnabled) {
- globalMappings = List.of();
- worldMappings = List.of();
- resolvedWorldMappings = Map.of();
- return;
- }
registerMappings();
- Map resolvedGlobalMappings = resolveMappings(globalMappings, true);
+
+ Map resolvedGlobalMappings = resolveMappings(globalMappings, true);
resolvedWorldMappings = resolveMappings(worldMappings, false);
- globalMigration = collectMigration(globalPath, Path.of(""), resolvedGlobalMappings);
- worldDefaultsMigration = collectMigration(worldDefaultsPath, Path.of(""), resolvedWorldMappings);
- migrateValues(globalMigration, leafGlobalConfig);
- migrateValues(worldDefaultsMigration, leafWorldDefaultsConfig);
+ globalMigration = collectMigration(globalPath, resolvedGlobalMappings);
+ worldDefaultsMigration = collectMigration(worldDefaultsPath, resolvedWorldMappings);
+
+ applyMigration(globalMigration, leafGlobalConfig);
+ applyMigration(worldDefaultsMigration, leafWorldDefaultsConfig);
}
private static void registerMappings() {
@@ -83,34 +75,23 @@ private static void registerMappings() {
addGlobalMapping("gameplay-mechanics.enable-book-writing", BookWriting.class, "enabled");
addWorldMapping("small-optimizations.save-fireworks", SaveFireworks.class, "enabled");
-
- globalMappings = List.copyOf(globalMappings);
- worldMappings = List.copyOf(worldMappings);
}
- private static void addGlobalMapping(
- String oldPath,
- Class> moduleClass,
- String fieldName
- ) {
+ private static void addGlobalMapping(String oldPath, Class extends ConfigModule> moduleClass, String fieldName) {
globalMappings.add(new Mapping(oldPath, moduleClass, fieldName));
}
- private static void addWorldMapping(
- String oldPath,
- Class> moduleClass,
- String fieldName
- ) {
+ private static void addWorldMapping(String oldPath, Class extends WorldConfigModule> moduleClass, String fieldName) {
worldMappings.add(new Mapping(oldPath, moduleClass, fieldName));
}
- public static void completeGlobal(Path leafFile) {
- complete(globalMigration, leafFile);
+ public static void finalizeGlobalMigration() {
+ finalizeMigration(globalMigration);
globalMigration = null;
}
- public static void completeWorldDefaults(Path leafFile) {
- complete(worldDefaultsMigration, leafFile);
+ public static void finalizeWorldDefaultsMigration() {
+ finalizeMigration(worldDefaultsMigration);
worldDefaultsMigration = null;
}
@@ -119,66 +100,35 @@ public static void completeWorldDefaults(Path leafFile) {
*
* @return the newly created Leaf override, or {@code null} when normal Leaf loading should continue
*/
- public static @Nullable LeafWorldConfig migrateWorldOverride(
- Path worldDirectory,
- File leafFile,
- LeafWorldConfig defaults
- ) {
- if (!migrationEnabled) {
- return null;
- }
-
+ public static @Nullable LeafWorldConfig migrateWorldOverride(Path worldDirectory, File leafFile, LeafWorldConfig defaults) {
Path leafPath = leafFile.toPath();
Path galePath = worldDirectory.resolve(WORLD_OVERRIDE_FILE);
- if (LeafWorldConfig.exists(leafFile)) {
- if (Files.isRegularFile(galePath)) {
- renameOverride(galePath, "A Leaf world override already exists for " + worldDirectory + '.');
- }
- return null;
- }
if (!Files.isRegularFile(galePath)) {
return null;
}
- PendingMigration migration = collectMigration(
- galePath,
- worldContext(worldDirectory),
- resolvedWorldMappings
- );
- if (migration == null) {
- return null;
- }
- if (!migration.hasValues()) {
- archive(migration);
- return null;
- }
- if (!migration.unmappedPaths().isEmpty()) {
- LOGGER.warn(
- "Gale world config {} has option path(s) without Leaf mappings: {}. Deferring the whole override migration.",
- galePath,
- migration.unmappedPaths()
- );
- return null;
- }
- if (!migration.invalidPaths().isEmpty()) {
- renameOverride(galePath, "The Gale world override could not be converted.");
- return null;
- }
-
boolean leafFileCreated = false;
try {
+ if (LeafWorldConfig.exists(leafFile)) {
+ LOGGER.warn(
+ "Could not migrate Gale world config {} because Leaf world override {} already exists.",
+ galePath, leafPath
+ );
+ return null;
+ }
+
+ PendingMigration migration = collectMigration(galePath, resolvedWorldMappings);
+ if (migration == null || !migration.hasValues()) {
+ return null;
+ }
+
Files.createFile(leafPath);
leafFileCreated = true;
ConfigFile leafConfig = ConfigFile.loadConfig(leafFile);
- migrateValues(migration, leafConfig);
+ applyMigration(migration, leafConfig);
LeafWorldConfig migrated = LeafWorldConfig.loadOverride(leafConfig, defaults);
migrated.saveConfig();
- if (!containsAll(leafPath, migration.values().keySet())) {
- throw new IllegalStateException("Not all Gale world values were written to " + leafPath);
- }
-
- archive(migration);
return migrated;
} catch (Exception exception) {
if (leafFileCreated) {
@@ -188,57 +138,36 @@ public static void completeWorldDefaults(Path leafFile) {
exception.addSuppressed(cleanupException);
}
}
- LOGGER.error("Failed to migrate Gale world config for {}; using Leaf world defaults.", worldDirectory, exception);
- renameOverride(galePath, "The Leaf world override could not be saved.");
+ LOGGER.error(
+ "Failed to migrate Gale world config {} for {}; using Leaf world defaults.",
+ galePath, worldDirectory, exception
+ );
return null;
+ } finally {
+ archiveWorldOverride(galePath, worldDirectory);
+ LOGGER.warn(
+ "Finished processing Gale world config for {}. Please manually check the Leaf configuration used by this world.",
+ worldDirectory
+ );
}
}
- private static void migrateValues(@Nullable PendingMigration migration, ConfigFile leafConfig) {
+ private static void applyMigration(@Nullable PendingMigration migration, ConfigFile leafConfig) {
if (migration == null) {
return;
}
migration.values().forEach(leafConfig::set);
}
- private static void complete(@Nullable PendingMigration migration, Path leafFile) {
+ private static void finalizeMigration(@Nullable PendingMigration migration) {
if (migration == null) {
return;
}
- List remainingPaths = new ArrayList<>(migration.unmappedPaths());
- remainingPaths.addAll(migration.invalidPaths());
- if (!remainingPaths.isEmpty()) {
- LOGGER.warn("Gale config {} still has unmigrated option path(s): {}. Leaving it in place.",
- migration.sourcePath(), remainingPaths);
- return;
- }
- try {
- if (!containsAll(leafFile, migration.values().keySet())) {
- LOGGER.error("Gale values were not all written to {}; leaving {} in place.",
- leafFile, migration.sourcePath());
- return;
- }
- } catch (Exception exception) {
- LOGGER.error("Failed to verify migrated Leaf config {}; leaving {} in place.",
- leafFile, migration.sourcePath(), exception);
- return;
- }
archive(migration);
}
- private static boolean containsAll(Path leafFile, Set paths) throws Exception {
- if (paths.isEmpty()) {
- return true;
- }
- ConfigFile config = ConfigFile.loadConfig(leafFile.toFile());
- return paths.stream().allMatch(config::contains);
- }
-
- private static Map resolveMappings(
- List mappings,
- boolean global
- ) {
- Map resolvedMappings = new LinkedHashMap<>();
+ private static Map resolveMappings(List mappings, boolean global) {
+ Map resolvedMappings = new LinkedHashMap<>();
for (Mapping mapping : mappings) {
String oldPath = mapping.oldPath();
if (oldPath.isBlank()) {
@@ -252,23 +181,12 @@ private static Map resolveMappings(
throw new IllegalArgumentException("Invalid Gale migration target "
+ mapping.moduleClass().getName() + '.' + mapping.fieldName(), exception);
}
- validateTarget(field, global);
- String leafPath = ConfigPathResolver.fieldPath(mapping.moduleClass(), field);
+ String leafPath = ConfigPaths.fieldPath(mapping.moduleClass(), field);
- resolvedMappings.put(oldPath, new ResolvedTarget(field, leafPath));
- }
- return Map.copyOf(resolvedMappings);
- }
-
- private static void validateTarget(Field field, boolean global) {
- Class> expectedModuleType = global ? ConfigModule.class : WorldConfigModule.class;
- if (!expectedModuleType.isAssignableFrom(field.getDeclaringClass())
- || !field.isAnnotationPresent(ConfigInfo.class)
- || Modifier.isStatic(field.getModifiers()) != global
- || Modifier.isFinal(field.getModifiers())) {
- throw new IllegalArgumentException("Invalid Gale migration target: " + field);
+ resolvedMappings.put(oldPath, leafPath);
}
+ return resolvedMappings;
}
private static Path worldContext(Path worldDirectory) {
@@ -279,31 +197,6 @@ private static Path worldContext(Path worldDirectory) {
: absolute.subpath(0, absolute.getNameCount());
}
- private static void renameOverride(Path source, String reason) {
- Path target = uniqueOldPath(source);
- try {
- Files.move(source, target);
- LOGGER.warn("{} Renamed Gale config from {} to {}.", reason, source, target);
- } catch (IOException exception) {
- LOGGER.error("Failed to rename Gale config {}; leaving it in place.", source, exception);
- }
- }
-
- private static Path uniqueOldPath(Path source) {
- Path target = source.resolveSibling(source.getFileName() + "_old");
- if (!Files.exists(target)) {
- return target;
- }
- String suffix = BACKUP_TIME_FORMAT.format(LocalDateTime.now());
- for (int counter = 0; ; counter++) {
- Path candidate = source.resolveSibling(source.getFileName() + "_old-" + suffix
- + (counter == 0 ? "" : '-' + Integer.toString(counter)));
- if (!Files.exists(candidate)) {
- return candidate;
- }
- }
- }
-
private static synchronized Path backupDirectory() throws IOException {
if (backupDirectory != null) {
return backupDirectory;
@@ -324,55 +217,27 @@ private static synchronized Path backupDirectory() throws IOException {
private static @Nullable PendingMigration collectMigration(
Path sourcePath,
- Path worldContext,
- Map mappings
+ Map mappings
) {
if (!Files.isRegularFile(sourcePath)) {
return null;
}
try {
ConfigFile config = ConfigFile.loadConfig(sourcePath.toFile());
- List valuePaths = new ArrayList<>();
- collectValuePaths(config, "", valuePaths);
- valuePaths.removeAll(IGNORED_PATHS);
-
- List unmappedPaths = valuePaths.stream()
- .filter(path -> !mappings.containsKey(path))
- .toList();
+ boolean hasValues = hasConfigValues(config, "");
Map values = new LinkedHashMap<>();
- List invalidPaths = new ArrayList<>();
- for (Map.Entry entry : mappings.entrySet()) {
+ for (Map.Entry entry : mappings.entrySet()) {
String oldPath = entry.getKey();
if (!config.contains(oldPath)) {
continue;
}
- Object oldValue = config.get(oldPath);
- if (oldValue == null || oldValue instanceof Map, ?>) {
- LOGGER.warn("Gale path '{}' in {} is not an option; leaving it unmigrated.",
- oldPath, sourcePath);
- invalidPaths.add(oldPath);
- continue;
- }
- try {
- ResolvedTarget target = entry.getValue();
- values.put(target.leafPath(), convertValue(oldValue, target.field()));
- } catch (IllegalArgumentException exception) {
- LOGGER.warn("Gale path '{}' in {} is invalid for {}; leaving it unmigrated.",
- oldPath, sourcePath, entry.getValue().field(), exception);
- invalidPaths.add(oldPath);
- }
+ // Gale has already validated the source option; migration preserves its raw value.
+ values.put(entry.getValue(), config.get(oldPath));
}
-
- Path backupPath = worldContext.toString().isEmpty()
- ? Path.of(sourcePath.getFileName().toString())
- : Path.of("world-overrides").resolve(worldContext).resolve(sourcePath.getFileName().toString());
return new PendingMigration(
sourcePath,
- backupPath,
Map.copyOf(values),
- List.copyOf(unmappedPaths),
- List.copyOf(invalidPaths),
- !valuePaths.isEmpty()
+ hasValues
);
} catch (Exception exception) {
LOGGER.error("Failed to read Gale config {}; migration was skipped.", sourcePath, exception);
@@ -382,71 +247,52 @@ private static synchronized Path backupDirectory() throws IOException {
private static void archive(PendingMigration migration) {
try {
- Path relative = migration.backupPath().normalize();
- if (relative.isAbsolute() || relative.startsWith("..")) {
- throw new IOException("Invalid Gale backup path: " + relative);
- }
- Path target = backupDirectory().resolve(relative).normalize();
- Files.createDirectories(target.getParent());
- Files.move(migration.sourcePath(), target);
- LOGGER.warn("Moved migrated Gale config {} to {}.", migration.sourcePath(), target);
+ Path backupPath = Path.of(migration.sourcePath().getFileName().toString());
+ moveToBackup(migration.sourcePath(), backupPath);
} catch (IOException exception) {
- LOGGER.error("Failed to back up Gale config {}; leaving it in place.",
- migration.sourcePath(), exception);
+ logBackupFailure(migration.sourcePath(), exception);
}
}
- private static void collectValuePaths(Map, ?> values, String parent, List paths) {
+ private static void archiveWorldOverride(Path sourcePath, Path worldDirectory) {
+ try {
+ Path fileName = Path.of(sourcePath.getFileName().toString());
+ Path backupPath = Path.of("world-overrides").resolve(worldContext(worldDirectory)).resolve(fileName);
+ moveToBackup(sourcePath, backupPath);
+ } catch (IOException exception) {
+ logBackupFailure(sourcePath, exception);
+ }
+ }
+
+ private static void moveToBackup(Path sourcePath, Path backupPath) throws IOException {
+ Path relative = backupPath.normalize();
+ if (relative.isAbsolute() || relative.startsWith("..")) {
+ throw new IOException("Invalid Gale backup path: " + relative);
+ }
+ Path target = backupDirectory().resolve(relative).normalize();
+ Files.createDirectories(target.getParent());
+ Files.move(sourcePath, target);
+ LOGGER.warn("Moved Gale config {} to {}.", sourcePath, target);
+ }
+
+ private static void logBackupFailure(Path sourcePath, IOException exception) {
+ LOGGER.error("Failed to back up Gale config {}; leaving it in place.", sourcePath, exception);
+ }
+
+ private static boolean hasConfigValues(Map, ?> values, String parent) {
for (Map.Entry, ?> entry : values.entrySet()) {
String name = String.valueOf(entry.getKey());
String path = parent.isEmpty() ? name : parent + '.' + name;
Object value = entry.getValue();
if (value instanceof Map, ?> nested) {
- collectValuePaths(nested, path, paths);
- } else if (value != null) {
- paths.add(path);
- }
- }
- }
-
- @SuppressWarnings({"unchecked", "rawtypes"})
- private static Object convertValue(Object value, Field field) {
- Class> type = field.getType();
- if (type == boolean.class || type == Boolean.class) {
- if (value instanceof Boolean booleanValue) {
- return booleanValue;
- }
- String booleanValue = String.valueOf(value);
- if (booleanValue.equalsIgnoreCase("true")) {
+ if (hasConfigValues(nested, path)) {
+ return true;
+ }
+ } else if (!IGNORED_PATHS.contains(path)) {
return true;
}
- if (booleanValue.equalsIgnoreCase("false")) {
- return false;
- }
- throw new IllegalArgumentException("Not a boolean: " + value);
- }
- if (type == int.class || type == Integer.class) {
- return value instanceof Number number ? number.intValue() : Integer.parseInt(String.valueOf(value));
- }
- if (type == long.class || type == Long.class) {
- return value instanceof Number number ? number.longValue() : Long.parseLong(String.valueOf(value));
- }
- if (type == double.class || type == Double.class) {
- return value instanceof Number number ? number.doubleValue() : Double.parseDouble(String.valueOf(value));
}
- if (type == String.class) {
- return String.valueOf(value);
- }
- if (List.class.isAssignableFrom(type) && value instanceof List> list) {
- return list.stream().map(String::valueOf).toList();
- }
- if (type.isEnum()) {
- return Enum.valueOf((Class extends Enum>) type, String.valueOf(value).toUpperCase(Locale.ROOT));
- }
- throw new IllegalArgumentException("Unsupported migrated config field type: " + field);
- }
-
- private record ResolvedTarget(Field field, String leafPath) {
+ return false;
}
private record Mapping(String oldPath, Class> moduleClass, String fieldName) {
@@ -454,10 +300,7 @@ private record Mapping(String oldPath, Class> moduleClass, String fieldName) {
private record PendingMigration(
Path sourcePath,
- Path backupPath,
Map values,
- List unmappedPaths,
- List invalidPaths,
boolean hasValues
) {
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/DynamicActivationofBrain.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/DynamicActivationofBrain.java
index d743cbd144..9e0ad6a4ac 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/DynamicActivationofBrain.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/DynamicActivationofBrain.java
@@ -70,7 +70,7 @@ public void onLoaded() {
}
@Override
- public void onPostLoaded() {
+ public void onRegistriesLoaded() {
for (EntityType> entityType : BuiltInRegistries.ENTITY_TYPE) {
entityType.dabEnabled = true; // reset all, before setting the ones to true
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigPathResolver.java b/leaf-server/src/main/java/org/dreeam/leaf/config/util/ConfigPaths.java
similarity index 92%
rename from leaf-server/src/main/java/org/dreeam/leaf/config/ConfigPathResolver.java
rename to leaf-server/src/main/java/org/dreeam/leaf/config/util/ConfigPaths.java
index 4019826d8d..5e190fd0ad 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigPathResolver.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/util/ConfigPaths.java
@@ -1,4 +1,4 @@
-package org.dreeam.leaf.config;
+package org.dreeam.leaf.config.util;
import org.dreeam.leaf.config.annotations.ConfigClassInfo;
import org.dreeam.leaf.config.annotations.ConfigInfo;
@@ -6,7 +6,7 @@
import java.lang.reflect.Field;
/** Resolves annotation-driven module and option paths. */
-public final class ConfigPathResolver {
+public final class ConfigPaths {
public static String modulePath(Class> moduleClass) {
ConfigClassInfo info = moduleClass.getAnnotation(ConfigClassInfo.class);
From 4de8bf845231c86e7e7835d7ae98fc6a55f2609d Mon Sep 17 00:00:00 2001
From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com>
Date: Fri, 14 Aug 2026 08:50:18 +0800
Subject: [PATCH 08/14] Cleanup
---
.../org/dreeam/leaf/config/LeafConfig.java | 5 +-
.../migration/gale/GaleConfigMigration.java | 147 ++++++------------
2 files changed, 47 insertions(+), 105 deletions(-)
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java
index 442b3ae172..58e3330b37 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java
@@ -53,7 +53,7 @@ public class LeafConfig {
private static final String REGION_COUNTRY_CODE = Locale.getDefault().getCountry().toUpperCase(Locale.ROOT);
private static final boolean IS_CHINESE_LOCALE = REGION_COUNTRY_CODE.equals("CN");
- protected static final File CONFIG_DIRECTORY = new File("config");
+ public static final File CONFIG_DIRECTORY = new File("config");
protected static final String CONFIG_MODULE_PACKAGE = "org.dreeam.leaf.config.modules";
protected static final String GLOBAL_CONFIG_FILE = "leaf-global.yml";
protected static final String DEFAULT_WORLD_CONFIG_FILE = "leaf-world-defaults.yml";
@@ -434,9 +434,8 @@ private static void purgeOutdated() {
String leafConfigV1 = "leaf.yml";
String leafConfigV2 = "leaf_config";
- Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyMMddhhmmss");
- String backupDir = "config/backup" + dateFormat.format(date) + "/";
+ String backupDir = "config/backup" + dateFormat.format(new Date()) + "/";
File pufferfishConfigFile = new File(pufferfishConfig);
File leafConfigV1File = new File(leafConfigV1);
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/migration/gale/GaleConfigMigration.java b/leaf-server/src/main/java/org/dreeam/leaf/config/migration/gale/GaleConfigMigration.java
index f781adb583..bf26ad2793 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/migration/gale/GaleConfigMigration.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/migration/gale/GaleConfigMigration.java
@@ -6,7 +6,6 @@
import org.dreeam.leaf.config.ConfigModule;
import org.dreeam.leaf.config.LeafWorldConfig;
import org.dreeam.leaf.config.WorldConfigModule;
-import org.dreeam.leaf.config.annotations.ConfigInfo;
import org.dreeam.leaf.config.modules.gameplay.BookWriting;
import org.dreeam.leaf.config.modules.opt.SaveFireworks;
import org.dreeam.leaf.config.util.ConfigPaths;
@@ -15,13 +14,11 @@
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.time.LocalDateTime;
-import java.time.format.DateTimeFormatter;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -34,7 +31,6 @@
public final class GaleConfigMigration {
private static final Logger LOGGER = LogManager.getLogger(GaleConfigMigration.class.getSimpleName());
- private static final DateTimeFormatter BACKUP_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss");
private static final String GLOBAL_FILE = "gale-global.yml";
private static final String WORLD_DEFAULTS_FILE = "gale-world-defaults.yml";
private static final String WORLD_OVERRIDE_FILE = "gale-world.yml";
@@ -44,28 +40,18 @@ public final class GaleConfigMigration {
private static List globalMappings = List.of();
private static List worldMappings = List.of();
private static Map resolvedWorldMappings = Map.of();
- private static PendingMigration globalMigration;
- private static PendingMigration worldDefaultsMigration;
- private static Path backupDirectory;
public static void migrate(Path directory, ConfigFile leafGlobalConfig, ConfigFile leafWorldDefaultsConfig) {
configDirectory = Objects.requireNonNull(directory, "directory").normalize();
- backupDirectory = null;
- globalMigration = null;
- worldDefaultsMigration = null;
Path globalPath = configDirectory.resolve(GLOBAL_FILE);
Path worldDefaultsPath = configDirectory.resolve(WORLD_DEFAULTS_FILE);
registerMappings();
-
- Map resolvedGlobalMappings = resolveMappings(globalMappings, true);
- resolvedWorldMappings = resolveMappings(worldMappings, false);
- globalMigration = collectMigration(globalPath, resolvedGlobalMappings);
- worldDefaultsMigration = collectMigration(worldDefaultsPath, resolvedWorldMappings);
-
- applyMigration(globalMigration, leafGlobalConfig);
- applyMigration(worldDefaultsMigration, leafWorldDefaultsConfig);
+ Map resolvedGlobalMappings = resolveMappings(globalMappings);
+ resolvedWorldMappings = resolveMappings(worldMappings);
+ migrateValues(globalPath, resolvedGlobalMappings, leafGlobalConfig);
+ migrateValues(worldDefaultsPath, resolvedWorldMappings, leafWorldDefaultsConfig);
}
private static void registerMappings() {
@@ -86,13 +72,11 @@ private static void addWorldMapping(String oldPath, Class extends WorldConfigM
}
public static void finalizeGlobalMigration() {
- finalizeMigration(globalMigration);
- globalMigration = null;
+ archive(configDirectory.resolve(GLOBAL_FILE));
}
public static void finalizeWorldDefaultsMigration() {
- finalizeMigration(worldDefaultsMigration);
- worldDefaultsMigration = null;
+ archive(configDirectory.resolve(WORLD_DEFAULTS_FILE));
}
/**
@@ -118,15 +102,15 @@ public static void finalizeWorldDefaultsMigration() {
return null;
}
- PendingMigration migration = collectMigration(galePath, resolvedWorldMappings);
- if (migration == null || !migration.hasValues()) {
+ ConfigFile galeConfig = loadSrcConfig(galePath);
+ if (galeConfig == null || !hasConfigValues(galeConfig, "")) {
return null;
}
Files.createFile(leafPath);
leafFileCreated = true;
ConfigFile leafConfig = ConfigFile.loadConfig(leafFile);
- applyMigration(migration, leafConfig);
+ applyMappings(galeConfig, resolvedWorldMappings, leafConfig);
LeafWorldConfig migrated = LeafWorldConfig.loadOverride(leafConfig, defaults);
migrated.saveConfig();
return migrated;
@@ -152,28 +136,31 @@ public static void finalizeWorldDefaultsMigration() {
}
}
- private static void applyMigration(@Nullable PendingMigration migration, ConfigFile leafConfig) {
- if (migration == null) {
+ private static void migrateValues(Path srcPath, Map mappings, ConfigFile leafConfig) {
+ ConfigFile srcConfig = loadSrcConfig(srcPath);
+ if (srcConfig == null) {
return;
}
- migration.values().forEach(leafConfig::set);
+ applyMappings(srcConfig, mappings, leafConfig);
}
- private static void finalizeMigration(@Nullable PendingMigration migration) {
- if (migration == null) {
- return;
+ private static void applyMappings(
+ ConfigFile srcConfig,
+ Map mappings,
+ ConfigFile leafConfig
+ ) {
+ for (Map.Entry entry : mappings.entrySet()) {
+ String oldPath = entry.getKey();
+ if (srcConfig.contains(oldPath)) {
+ leafConfig.set(entry.getValue(), srcConfig.get(oldPath));
+ }
}
- archive(migration);
}
- private static Map resolveMappings(List mappings, boolean global) {
+ private static Map resolveMappings(List mappings) {
Map resolvedMappings = new LinkedHashMap<>();
for (Mapping mapping : mappings) {
String oldPath = mapping.oldPath();
- if (oldPath.isBlank()) {
- throw new IllegalArgumentException("Gale config path must not be blank");
- }
-
Field field;
try {
field = mapping.moduleClass().getDeclaredField(mapping.fieldName());
@@ -197,86 +184,49 @@ private static Path worldContext(Path worldDirectory) {
: absolute.subpath(0, absolute.getNameCount());
}
- private static synchronized Path backupDirectory() throws IOException {
- if (backupDirectory != null) {
- return backupDirectory;
- }
- Path root = configDirectory.resolve("backup");
- Files.createDirectories(root);
- String name = "backup-" + BACKUP_TIME_FORMAT.format(LocalDateTime.now());
- for (int counter = 0; ; counter++) {
- Path candidate = root.resolve(counter == 0 ? name : name + '-' + counter);
- try {
- Files.createDirectory(candidate);
- backupDirectory = candidate;
- return candidate;
- } catch (FileAlreadyExistsException ignored) {
- }
- }
- }
-
- private static @Nullable PendingMigration collectMigration(
- Path sourcePath,
- Map mappings
- ) {
- if (!Files.isRegularFile(sourcePath)) {
+ private static @Nullable ConfigFile loadSrcConfig(Path srcPath) {
+ if (!Files.isRegularFile(srcPath)) {
return null;
}
try {
- ConfigFile config = ConfigFile.loadConfig(sourcePath.toFile());
- boolean hasValues = hasConfigValues(config, "");
- Map values = new LinkedHashMap<>();
- for (Map.Entry entry : mappings.entrySet()) {
- String oldPath = entry.getKey();
- if (!config.contains(oldPath)) {
- continue;
- }
- // Gale has already validated the source option; migration preserves its raw value.
- values.put(entry.getValue(), config.get(oldPath));
- }
- return new PendingMigration(
- sourcePath,
- Map.copyOf(values),
- hasValues
- );
+ return ConfigFile.loadConfig(srcPath.toFile());
} catch (Exception exception) {
- LOGGER.error("Failed to read Gale config {}; migration was skipped.", sourcePath, exception);
+ LOGGER.error("Failed to read Gale config {}; migration was skipped.", srcPath, exception);
return null;
}
}
- private static void archive(PendingMigration migration) {
+ private static void archive(Path srcPath) {
+ if (!Files.isRegularFile(srcPath)) return;
try {
- Path backupPath = Path.of(migration.sourcePath().getFileName().toString());
- moveToBackup(migration.sourcePath(), backupPath);
+ Path backupPath = Path.of(srcPath.getFileName().toString());
+ moveToBackup(srcPath, backupPath);
} catch (IOException exception) {
- logBackupFailure(migration.sourcePath(), exception);
+ LOGGER.error("Failed to back up Gale config {}; leaving it in place.", srcPath, exception);
}
}
- private static void archiveWorldOverride(Path sourcePath, Path worldDirectory) {
+ private static void archiveWorldOverride(Path srcPath, Path worldDirectory) {
try {
- Path fileName = Path.of(sourcePath.getFileName().toString());
+ Path fileName = Path.of(srcPath.getFileName().toString());
Path backupPath = Path.of("world-overrides").resolve(worldContext(worldDirectory)).resolve(fileName);
- moveToBackup(sourcePath, backupPath);
+ moveToBackup(srcPath, backupPath);
} catch (IOException exception) {
- logBackupFailure(sourcePath, exception);
+ LOGGER.error("Failed to back up Gale config {}; leaving it in place.", srcPath, exception);
}
}
- private static void moveToBackup(Path sourcePath, Path backupPath) throws IOException {
+ private static void moveToBackup(Path srcPath, Path backupPath) throws IOException {
Path relative = backupPath.normalize();
if (relative.isAbsolute() || relative.startsWith("..")) {
throw new IOException("Invalid Gale backup path: " + relative);
}
- Path target = backupDirectory().resolve(relative).normalize();
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyMMddhhmmss");
+ Path backupDirectory = configDirectory.resolve("backup" + dateFormat.format(new Date()));
+ Path target = backupDirectory.resolve(relative).normalize();
Files.createDirectories(target.getParent());
- Files.move(sourcePath, target);
- LOGGER.warn("Moved Gale config {} to {}.", sourcePath, target);
- }
-
- private static void logBackupFailure(Path sourcePath, IOException exception) {
- LOGGER.error("Failed to back up Gale config {}; leaving it in place.", sourcePath, exception);
+ Files.move(srcPath, target);
+ LOGGER.warn("Moved Gale config {} to {}.", srcPath, target);
}
private static boolean hasConfigValues(Map, ?> values, String parent) {
@@ -297,11 +247,4 @@ private static boolean hasConfigValues(Map, ?> values, String parent) {
private record Mapping(String oldPath, Class> moduleClass, String fieldName) {
}
-
- private record PendingMigration(
- Path sourcePath,
- Map values,
- boolean hasValues
- ) {
- }
}
From 168dbeb844f01a394a9a02b6a04b7a6f0eace763 Mon Sep 17 00:00:00 2001
From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com>
Date: Fri, 14 Aug 2026 10:01:26 +0800
Subject: [PATCH 09/14] Cleanup
---
.../org/dreeam/leaf/config/ConfigBinder.java | 140 ++++++++---
.../org/dreeam/leaf/config/ConfigModule.java | 15 --
.../leaf/config/ConfigModuleLoader.java | 152 ------------
.../org/dreeam/leaf/config/LeafConfig.java | 219 ++++++++++++++---
.../leaf/config/LeafConfigAccessor.java | 8 +-
.../dreeam/leaf/config/LeafWorldConfig.java | 224 ++----------------
.../dreeam/leaf/config/WorldConfigModule.java | 3 +-
.../migration/gale/GaleConfigMigration.java | 12 +-
.../leaf/config/modules/misc/SecureSeed.java | 2 +-
9 files changed, 325 insertions(+), 450 deletions(-)
delete mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModuleLoader.java
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigBinder.java b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigBinder.java
index bac2970552..3b9ea96309 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigBinder.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigBinder.java
@@ -9,6 +9,7 @@
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
+import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@@ -17,27 +18,9 @@
*/
final class ConfigBinder {
- private ConfigBinder() {
- }
-
- static void bindGlobal(
- ConfigModule module,
- LeafConfigAccessor config,
- boolean alreadyInitialized
- ) throws IllegalAccessException {
- bind(module, config, true, alreadyInitialized);
- }
-
- static void bindWorld(
- WorldConfigModule module,
- LeafWorldConfig config,
- boolean alreadyInitialized
- ) throws IllegalAccessException {
- bind(module, config, false, alreadyInitialized);
- }
-
- private static void bind(
+ public static void bind(
Object module,
+ @Nullable Object worldDefaultModule,
LeafConfigAccessor config,
boolean global,
boolean alreadyInitialized
@@ -51,7 +34,7 @@ private static void bind(
String basePath = ConfigPaths.modulePath(moduleClass);
String sectionComment = config.pickStringRegionBased(classInfo.comments());
- if (sectionComment != null && (!(config instanceof LeafWorldConfig worldConfig) || worldConfig.isDefaultsConfig())) {
+ if (sectionComment != null && (!(config instanceof LeafWorldConfig worldConfig) || worldConfig.isWorldDefaultsFile())) {
config.addComment(basePath, sectionComment);
}
@@ -68,22 +51,76 @@ private static void bind(
continue;
}
- validateField(moduleClass, field, global);
- field.setAccessible(true);
+ if (global) {
+ bindGlobal(moduleClass, field, configInfo, config, skipReload);
+ } else {
+ bindWorld(module, worldDefaultModule, moduleClass, field, configInfo, config, skipReload);
+ }
+ }
+ }
+
+ private static void bindGlobal(
+ Class> moduleClass,
+ Field field,
+ ConfigInfo configInfo,
+ LeafConfigAccessor config,
+ boolean skipReload
+ ) throws IllegalAccessException {
+ validateField(moduleClass, field, true);
+ field.setAccessible(true);
+
+ String path = ConfigPaths.fieldPath(moduleClass, field);
+ Object defaultValue = field.get(null);
+ if (defaultValue == null) {
+ throw new IllegalStateException("Configuration field has a null default value: " + field);
+ }
- Object target = global ? null : module;
- String path = ConfigPaths.fieldPath(moduleClass, field);
- Object defaultValue = field.get(target);
- String comment = config.pickStringRegionBased(configInfo.comments());
- // Always call readValue, to keep comments on reloading
- Object loadedValue = readValue(config, path, comment, field, defaultValue);
+ String comment = config.pickStringRegionBased(configInfo.comments());
+ // Always call readValue, to keep comments on reloading
+ Object loadedValue = readValue(config, path, comment, field, defaultValue, true);
+ if (!skipReload) {
+ field.set(null, loadedValue);
+ }
+ }
+ private static void bindWorld(
+ Object module,
+ @Nullable Object defaultsModule,
+ Class> moduleClass,
+ Field field,
+ ConfigInfo configInfo,
+ LeafConfigAccessor config,
+ boolean skipReload
+ ) throws IllegalAccessException {
+ validateField(moduleClass, field, false);
+ field.setAccessible(true);
+
+ String path = ConfigPaths.fieldPath(moduleClass, field);
+ Object defaultValue = defaultsModule == null
+ ? field.get(module) // World default
+ : copyValue(field.get(defaultsModule)); // World override copy from defaults
+ if (defaultValue == null) {
+ throw new IllegalStateException("Configuration field has a null default value: " + field);
+ }
+
+ boolean worldOverridden = defaultsModule != null;
+ if (worldOverridden && !config.contains(path)) {
if (!skipReload) {
- field.set(target, loadedValue);
+ // Use world default if no override path defined
+ field.set(module, defaultValue);
}
+ return;
+ }
+
+ String comment = config.pickStringRegionBased(configInfo.comments());
+ // Always call readValue, to keep comments on reloading.
+ Object loadedValue = readValue(config, path, comment, field, defaultValue, !worldOverridden);
+ if (!skipReload) {
+ field.set(module, loadedValue);
}
}
+ // TODO[To-GitHub-issue]: Not sure whether needs to validate, we don't expose LeafConfig as public framework
private static void validateField(Class> moduleClass, Field field, boolean global) {
int modifiers = field.getModifiers();
if (Modifier.isFinal(modifiers)) {
@@ -103,47 +140,67 @@ private static Object readValue(
String path,
@Nullable String comment,
Field field,
- @Nullable Object defaultValue
+ Object defaultValue,
+ boolean writeDefault
) {
- if (defaultValue == null) {
- throw new IllegalStateException("Configuration field has a null default value: " + field);
- }
-
Class> type = field.getType();
if (type == boolean.class || type == Boolean.class) {
+ if (!writeDefault) {
+ return config.configFile.getBoolean(path, (Boolean) defaultValue);
+ }
return comment == null
? config.getBoolean(path, (Boolean) defaultValue)
: config.getBoolean(path, (Boolean) defaultValue, comment);
}
if (type == int.class || type == Integer.class) {
+ if (!writeDefault) {
+ return config.configFile.getInteger(path, (Integer) defaultValue);
+ }
return comment == null
? config.getInt(path, (Integer) defaultValue)
: config.getInt(path, (Integer) defaultValue, comment);
}
if (type == long.class || type == Long.class) {
+ if (!writeDefault) {
+ return config.configFile.getLong(path, (Long) defaultValue);
+ }
return comment == null
? config.getLong(path, (Long) defaultValue)
: config.getLong(path, (Long) defaultValue, comment);
}
if (type == double.class || type == Double.class) {
+ if (!writeDefault) {
+ return config.configFile.getDouble(path, (Double) defaultValue);
+ }
return comment == null
? config.getDouble(path, (Double) defaultValue)
: config.getDouble(path, (Double) defaultValue, comment);
}
if (type == String.class) {
+ if (!writeDefault) {
+ return config.configFile.getString(path, (String) defaultValue);
+ }
return comment == null
? config.getString(path, (String) defaultValue)
: config.getString(path, (String) defaultValue, comment);
}
if (List.class.isAssignableFrom(type)) {
+ if (!writeDefault) {
+ return config.configFile.getStringList(path);
+ }
return comment == null
? config.getList(path, (List) defaultValue)
: config.getList(path, (List) defaultValue, comment);
}
if (type.isEnum()) {
- String value = comment == null
- ? config.getString(path, ((Enum>) defaultValue).name())
- : config.getString(path, ((Enum>) defaultValue).name(), comment);
+ String value;
+ if (!writeDefault) {
+ value = config.configFile.getString(path, ((Enum>) defaultValue).name());
+ } else {
+ value = comment == null
+ ? config.getString(path, ((Enum>) defaultValue).name())
+ : config.getString(path, ((Enum>) defaultValue).name(), comment);
+ }
try {
return Enum.valueOf((Class extends Enum>) type, value.toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException exception) {
@@ -153,4 +210,11 @@ private static Object readValue(
}
throw new IllegalArgumentException("Unsupported @ConfigInfo field type: " + field);
}
+
+ private static Object copyValue(Object value) {
+ if (value instanceof List> list) {
+ return new ArrayList<>(list);
+ }
+ return value;
+ }
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModule.java b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModule.java
index 8c59760964..0e390b41f5 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModule.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModule.java
@@ -1,7 +1,5 @@
package org.dreeam.leaf.config;
-import java.lang.reflect.InvocationTargetException;
-
/**
* Marker and lifecycle contract for a server-wide Leaf configuration module.
*
@@ -23,17 +21,4 @@ default void onLoaded() {
*/
default void onRegistriesLoaded() {
}
-
- static void initModules()
- throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
- ConfigModuleLoader.initModules();
- }
-
- static void loadAfterBootstrap() {
- ConfigModuleLoader.loadAfterBootstrap();
- }
-
- static void clearModules() {
- ConfigModuleLoader.clearModules();
- }
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModuleLoader.java b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModuleLoader.java
deleted file mode 100644
index 16e95606df..0000000000
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModuleLoader.java
+++ /dev/null
@@ -1,152 +0,0 @@
-package org.dreeam.leaf.config;
-
-import it.unimi.dsi.fastutil.objects.ObjectArrays;
-import org.dreeam.leaf.config.annotations.ConfigClassInfo;
-import org.dreeam.leaf.config.annotations.Experimental;
-
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Set;
-
-final class ConfigModuleLoader {
-
- private static final Set LOADED_MODULES = new LinkedHashSet<>();
- private static List> worldModules = List.of();
- private static boolean alreadyInitialized;
-
- private ConfigModuleLoader() {
- }
-
- static void initModules()
- throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
- List enabledExperimentalModules = new ArrayList<>();
- List deprecatedModules = new ArrayList<>();
- List> discoveredWorldModules = new ArrayList<>();
-
- Class>[] classes = LeafConfig.getClasses(LeafConfig.CONFIG_MODULE_PACKAGE).toArray(new Class[0]);
- ObjectArrays.quickSort(classes, Comparator.comparing((Class> clazz) -> clazz.getSimpleName())
- .thenComparing(Class::getName));
- for (Class> moduleClass : classes) {
- if (moduleClass.isInterface()
- || Modifier.isAbstract(moduleClass.getModifiers())) {
- continue;
- }
-
- if (WorldConfigModule.class.isAssignableFrom(moduleClass)) {
- @SuppressWarnings("unchecked")
- Class extends WorldConfigModule> worldModuleClass =
- (Class extends WorldConfigModule>) moduleClass;
- discoveredWorldModules.add(worldModuleClass);
- validateAnnotatedModule(moduleClass);
- continue;
- }
- if (!ConfigModule.class.isAssignableFrom(moduleClass)) {
- continue;
- }
-
- ConfigModule module = (ConfigModule) moduleClass.getConstructor().newInstance();
- validateAnnotatedModule(moduleClass);
- ConfigBinder.bindGlobal(module, LeafConfig.globalConfig(), alreadyInitialized);
- module.onLoaded();
- LOADED_MODULES.add(module);
- collectEnabledFields(moduleClass, Experimental.class, enabledExperimentalModules);
- collectEnabledFields(moduleClass, Deprecated.class, deprecatedModules);
- }
-
- if (!enabledExperimentalModules.isEmpty()) {
- LeafConfig.LOGGER.warn(
- "You have following experimental module(s) enabled: {}, please proceed with caution!",
- formatFields(enabledExperimentalModules)
- );
- }
- if (!deprecatedModules.isEmpty()) {
- LeafConfig.LOGGER.warn(
- "The following enabled module(s) has been deprecated: {}, please proceed with caution!",
- formatFields(deprecatedModules)
- );
- }
- worldModules = List.copyOf(discoveredWorldModules);
- }
-
- static void loadAfterBootstrap() {
- for (ConfigModule module : LOADED_MODULES) {
- module.onRegistriesLoaded();
- }
-
- try {
- LeafConfig.globalConfig().saveConfig();
- LeafConfig.finalizeGlobalConfigMigration();
- } catch (Exception exception) {
- LeafConfig.LOGGER.error("Failed to save config file!", exception);
- }
- }
-
- static void loadWorldModules(LeafWorldConfig config) {
- try {
- boolean alreadyInitialized = config.isReload();
- for (Class extends WorldConfigModule> moduleClass : worldModules) {
- loadWorldModule(config, moduleClass, alreadyInitialized);
- }
- } catch (ReflectiveOperationException exception) {
- throw new RuntimeException("Could not load Leaf world configuration modules", exception);
- }
- }
-
- private static void loadWorldModule(
- LeafWorldConfig config,
- Class moduleClass,
- boolean alreadyInitialized
- ) throws ReflectiveOperationException {
- T module = alreadyInitialized ? config.reloadModule(moduleClass) : null;
- if (module == null) {
- module = moduleClass.getConstructor().newInstance();
- }
-
- ConfigBinder.bindWorld(module, config, alreadyInitialized);
- config.registerModule(moduleClass, module);
- }
-
- static void clearModules() {
- LOADED_MODULES.clear();
- worldModules = List.of();
- }
-
- static void markInitialized() {
- alreadyInitialized = true;
- }
-
- private static void validateAnnotatedModule(Class> moduleClass) {
- if (!moduleClass.isAnnotationPresent(ConfigClassInfo.class)) {
- throw new IllegalStateException("Configuration module " + moduleClass.getName()
- + " is missing @ConfigClassInfo");
- }
- }
-
- private static void collectEnabledFields(
- Class> moduleClass,
- Class extends Annotation> annotation,
- List enabledFields
- ) throws IllegalAccessException {
- for (Field field : moduleClass.getDeclaredFields()) {
- if (!field.isAnnotationPresent(annotation) || !Modifier.isStatic(field.getModifiers())) {
- continue;
- }
- field.setAccessible(true);
- if (field.get(null) instanceof Boolean enabled && enabled) {
- enabledFields.add(field);
- }
- }
- }
-
- private static List formatFields(List fields) {
- return fields.stream()
- .map(field -> field.getDeclaringClass().getSimpleName() + "." + field.getName())
- .toList();
- }
-}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java
index 58e3330b37..f81c97c6cf 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java
@@ -2,15 +2,19 @@
import io.github.thatsmusic99.configurationmaster.api.ConfigFile;
import io.papermc.paper.SparksFly;
+import it.unimi.dsi.fastutil.objects.ObjectArrays;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
-import net.minecraft.util.Util;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ServerLevel;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import org.dreeam.leaf.config.annotations.Experimental;
import org.dreeam.leaf.config.migration.LeafConfigMigration;
import org.dreeam.leaf.config.modules.misc.SentryDSN;
import org.dreeam.leaf.config.migration.gale.GaleConfigMigration;
import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.command.Command;
@@ -18,6 +22,8 @@
import java.io.File;
import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
@@ -29,6 +35,7 @@
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Comparator;
import java.util.Date;
import java.util.Enumeration;
import java.util.LinkedHashSet;
@@ -65,19 +72,24 @@ public class LeafConfig {
private static LeafGlobalConfig globalConfig;
private static LeafWorldConfig worldDefaultsConfig;
+ private static final List GLOBAL_MODULES = new ArrayList<>();
+ private static final List WORLD_MODULES = new ArrayList<>();
+ private static boolean modulesInitialized;
+
private static ConfigVersion previousConfigVersion = ConfigVersion.initial();
/* Load & Reload */
- // Reload config (async)
+ // Reload config on the server thread
public static CompletableFuture reloadAsync(CommandSender sender) {
+ MinecraftServer server = MinecraftServer.getServer();
return CompletableFuture.runAsync(() -> {
try {
long begin = System.nanoTime();
- ConfigModule.clearModules();
loadConfig(false);
- ConfigModule.loadAfterBootstrap();
+ reloadWorldConfig(server);
+ loadAfterBootstrap();
final String success = String.format("Successfully reloaded config in %sms.", (System.nanoTime() - begin) / 1_000_000);
Command.broadcastCommandMessage(sender, Component.text(success, NamedTextColor.GREEN));
@@ -85,7 +97,15 @@ public static CompletableFuture reloadAsync(CommandSender sender) {
Command.broadcastCommandMessage(sender, Component.text("Failed to reload config. See error in console!", NamedTextColor.RED));
LOGGER.error("Failed to reload config!", e);
}
- }, Util.ioPool());
+ }, server);
+ }
+
+ private static void reloadWorldConfig(MinecraftServer server) {
+ for (ServerLevel level : server.getAllLevels()) {
+ Path worldDirectory = server.storageSource.getDimensionPath(level.dimension());
+ LeafWorldConfig reloadedConfig = loadWorldConfig(worldDirectory, true);
+ level.setLeafConfig(reloadedConfig);
+ }
}
// Init config
@@ -109,40 +129,31 @@ private static void loadConfig(boolean init) throws Exception {
// Create config folder
createDirectory(CONFIG_DIRECTORY);
- File globalConfigFile = new File(CONFIG_DIRECTORY, GLOBAL_CONFIG_FILE);
- File worldDefaultsFile = new File(CONFIG_DIRECTORY, DEFAULT_WORLD_CONFIG_FILE);
-
- if (!worldDefaultsFile.exists()) {
- Files.createFile(worldDefaultsFile.toPath());
- }
-
- ConfigFile globalConfigFileData = ConfigFile.loadConfig(globalConfigFile);
- ConfigFile worldDefaultsFileData = ConfigFile.loadConfig(worldDefaultsFile);
+ ConfigFile globalConfigFile = ConfigFile.loadConfig(new File(CONFIG_DIRECTORY, GLOBAL_CONFIG_FILE));
+ ConfigFile worldDefaultsFile = ConfigFile.loadConfig(new File(CONFIG_DIRECTORY, DEFAULT_WORLD_CONFIG_FILE));
if (init) {
// Migrate the same raw config instances that will be bound and saved below.
- LeafConfigMigration.migrate(globalConfigFileData, worldDefaultsFileData);
+ LeafConfigMigration.migrate(globalConfigFile, worldDefaultsFile);
GaleConfigMigration.migrate(
CONFIG_DIRECTORY.toPath(),
- globalConfigFileData,
- worldDefaultsFileData
+ globalConfigFile,
+ worldDefaultsFile
);
}
- globalConfig = new LeafGlobalConfig(globalConfigFileData);
+ globalConfig = new LeafGlobalConfig(globalConfigFile);
- // Load config modules
- ConfigModule.initModules();
+ loadGlobalModules();
- LeafWorldConfig previousWorldDefaults = worldDefaultsConfig;
- worldDefaultsConfig = LeafWorldConfig.loadDefaults(
- worldDefaultsFileData,
- previousWorldDefaults
+ worldDefaultsConfig = loadWorldDefaults(
+ worldDefaultsFile,
+ !init
);
worldDefaultsConfig.saveConfig();
GaleConfigMigration.finalizeWorldDefaultsMigration();
- ConfigModuleLoader.markInitialized();
+ modulesInitialized = true;
}
public static LeafGlobalConfig globalConfig() {
@@ -156,26 +167,176 @@ public static LeafWorldConfig worldDefaultsConfig() {
/**
* Loads an explicit world override without creating a file when the world uses the defaults.
*/
- public static LeafWorldConfig createWorldConfig(Path worldDirectory) {
+ public static LeafWorldConfig initWorldConfig(Path worldDirectory) {
File worldConfigFile = worldDirectory.resolve(WORLD_CONFIG_FILE).toFile();
LeafWorldConfig migratedConfig = GaleConfigMigration.migrateWorldOverride(
worldDirectory,
worldConfigFile,
- worldDefaultsConfig
+ worldDefaultsConfig,
+ false
);
if (migratedConfig != null) {
return migratedConfig;
}
- if (!LeafWorldConfig.exists(worldConfigFile)) {
+ if (!worldConfigFile.isFile()) {
+ return worldDefaultsConfig;
+ }
+ try {
+ return loadWorldOverride(ConfigFile.loadConfig(worldConfigFile), worldDefaultsConfig, false);
+ } catch (Exception exception) {
+ throw new RuntimeException("Could not load Leaf world config for " + worldDirectory, exception);
+ }
+ }
+
+ private static LeafWorldConfig loadWorldConfig(Path worldDirectory, boolean reload) {
+ File worldConfigFile = worldDirectory.resolve(WORLD_CONFIG_FILE).toFile();
+
+ if (!worldConfigFile.isFile()) {
return worldDefaultsConfig;
}
try {
- return new LeafWorldConfig(worldConfigFile, worldDefaultsConfig);
+ return loadWorldOverride(ConfigFile.loadConfig(worldConfigFile), worldDefaultsConfig, reload);
} catch (Exception exception) {
throw new RuntimeException("Could not load Leaf world config for " + worldDirectory, exception);
}
}
+ private static void discoverGlobalModules() throws ReflectiveOperationException {
+ Class>[] classes = getClasses(CONFIG_MODULE_PACKAGE).toArray(new Class[0]);
+ ObjectArrays.quickSort(classes, Comparator.comparing((Class> clazz) -> clazz.getSimpleName())
+ .thenComparing(Class::getName));
+ for (Class> moduleClass : classes) {
+ if (moduleClass.isInterface() || Modifier.isAbstract(moduleClass.getModifiers())) {
+ continue;
+ }
+
+ if (!ConfigModule.class.isAssignableFrom(moduleClass)) {
+ continue;
+ }
+
+ ConfigModule module = (ConfigModule) moduleClass.getConstructor().newInstance();
+ GLOBAL_MODULES.add(module);
+ }
+ }
+
+ private static void discoverWorldModules() {
+ Field[] fields = LeafWorldConfig.class.getDeclaredFields();
+ ObjectArrays.quickSort(fields, Comparator.comparing((Field field) -> field.getType().getSimpleName())
+ .thenComparing(field -> field.getType().getName()));
+ for (Field field : fields) {
+ if (WorldConfigModule.class.isAssignableFrom(field.getType())) {
+ WORLD_MODULES.add(field);
+ }
+ }
+ }
+
+ private static void loadGlobalModules() throws ReflectiveOperationException {
+ if (GLOBAL_MODULES.isEmpty()) {
+ discoverGlobalModules();
+ }
+
+ List enabledExperimentalModules = new ArrayList<>();
+ List deprecatedModules = new ArrayList<>();
+
+ for (ConfigModule module : GLOBAL_MODULES) {
+ ConfigBinder.bind(module, null, globalConfig, true, modulesInitialized);
+ module.onLoaded();
+ GLOBAL_MODULES.add(module);
+
+ Class> moduleClass = module.getClass();
+ collectEnabledFields(moduleClass, enabledExperimentalModules, deprecatedModules);
+ }
+
+ warnEnabledModules(
+ enabledExperimentalModules,
+ "You have following experimental module(s) enabled: {}, please proceed with caution!"
+ );
+ warnEnabledModules(
+ deprecatedModules,
+ "The following enabled module(s) has been deprecated: {}, please proceed with caution!"
+ );
+ }
+
+ private static LeafWorldConfig loadWorldDefaults(
+ ConfigFile configFile,
+ boolean reload
+ ) throws ReflectiveOperationException {
+ if (WORLD_MODULES.isEmpty()) {
+ discoverWorldModules();
+ }
+
+ LeafWorldConfig config = new LeafWorldConfig(configFile, LeafWorldConfig.Source.WORLD_DEFAULTS_FILE);
+
+ for (Field moduleField : WORLD_MODULES) {
+ WorldConfigModule module = (WorldConfigModule) moduleField.get(config);
+ ConfigBinder.bind(module, null, config, false, reload);
+ }
+
+ return config;
+ }
+
+ public static LeafWorldConfig loadWorldOverride(
+ ConfigFile configFile,
+ LeafWorldConfig worldDefaults,
+ boolean reload
+ ) throws ReflectiveOperationException {
+ LeafWorldConfig config = new LeafWorldConfig(configFile, LeafWorldConfig.Source.WORLD_OVERRIDE_FILE);
+
+ for (Field moduleField : WORLD_MODULES) {
+ WorldConfigModule module = (WorldConfigModule) moduleField.get(config);
+
+ WorldConfigModule worldDefaultModule = (WorldConfigModule) moduleField.get(worldDefaults);
+ ConfigBinder.bind(module, worldDefaultModule, config, false, reload);
+ }
+
+ return config;
+ }
+
+ public static void loadAfterBootstrap() {
+ for (ConfigModule module : GLOBAL_MODULES) {
+ module.onRegistriesLoaded();
+ }
+
+ try {
+ globalConfig.saveConfig();
+ finalizeGlobalConfigMigration();
+ } catch (Exception exception) {
+ LOGGER.error("Failed to save config file!", exception);
+ }
+ }
+
+ private static void collectEnabledFields(
+ Class> moduleClass,
+ List enabledExperimentalFields,
+ List enabledDeprecatedFields
+ ) throws IllegalAccessException {
+ for (Field field : moduleClass.getDeclaredFields()) {
+ boolean experimental = field.isAnnotationPresent(Experimental.class);
+ boolean deprecated = field.isAnnotationPresent(Deprecated.class);
+ if ((!experimental && !deprecated) || !Modifier.isStatic(field.getModifiers())) {
+ continue;
+ }
+ field.setAccessible(true);
+ if (field.get(null) instanceof Boolean enabled && enabled) {
+ if (experimental) {
+ enabledExperimentalFields.add(field);
+ }
+ if (deprecated) {
+ enabledDeprecatedFields.add(field);
+ }
+ }
+ }
+ }
+
+ private static void warnEnabledModules(List fields, String message) {
+ if (fields.isEmpty()) {
+ return;
+ }
+ LOGGER.warn(message, fields.stream()
+ .map(field -> field.getDeclaringClass().getSimpleName() + "." + field.getName())
+ .toList());
+ }
+
static void finalizeGlobalConfigMigration() {
GaleConfigMigration.finalizeGlobalMigration();
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigAccessor.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigAccessor.java
index e6a1ee17cc..e18c8959c5 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigAccessor.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigAccessor.java
@@ -14,10 +14,6 @@ abstract class LeafConfigAccessor {
protected final ConfigFile configFile;
- protected LeafConfigAccessor(File file) throws Exception {
- this(ConfigFile.loadConfig(file));
- }
-
protected LeafConfigAccessor(ConfigFile configFile) {
this.configFile = configFile;
}
@@ -26,6 +22,10 @@ public void saveConfig() throws Exception {
configFile.save();
}
+ boolean contains(String path) {
+ return configFile.contains(path);
+ }
+
public boolean migratePath(String oldPath, String newPath) {
return ConfigPathMigration.migrate(configFile, oldPath, newPath);
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafWorldConfig.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafWorldConfig.java
index b2e58a0d1f..93c0c9d1c2 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafWorldConfig.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafWorldConfig.java
@@ -1,229 +1,39 @@
package org.dreeam.leaf.config;
import io.github.thatsmusic99.configurationmaster.api.ConfigFile;
-import io.github.thatsmusic99.configurationmaster.api.ConfigSection;
-import org.jspecify.annotations.Nullable;
-
-import java.io.File;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
+import org.dreeam.leaf.config.modules.misc.WorldConfigExample;
+import org.dreeam.leaf.config.modules.opt.SaveFireworks;
/**
* An optional world-level overlay for {@link LeafConfig#worldDefaultsConfig()}.
*
- * The file is never created by this class. Callers must check {@link #exists()} before
- * constructing it, so worlds without {@code leaf-world.yml} use the shared defaults directly.
+ * The file is never created by this class. Worlds without {@code leaf-world.yml} use the
+ * shared defaults directly. World modules are exposed as typed fields for direct access through
+ * a level's Leaf configuration.
*/
public final class LeafWorldConfig extends LeafConfigAccessor {
- private final @Nullable LeafWorldConfig defaults;
- private LeafWorldConfig reloadSource;
- private final Map, WorldConfigModule> modules = new LinkedHashMap<>();
- public boolean secureSeedEnabled;
-
- public static LeafWorldConfig loadDefaults(File file) throws Exception {
- return loadDefaults(file, null);
- }
-
- static LeafWorldConfig loadDefaults(File file, LeafWorldConfig reloadSource) throws Exception {
- return new LeafWorldConfig(file, null, reloadSource);
- }
-
- static LeafWorldConfig loadDefaults(
- ConfigFile configFile,
- LeafWorldConfig reloadSource
- ) {
- return new LeafWorldConfig(configFile, null, reloadSource);
+ enum Source {
+ WORLD_DEFAULTS_FILE,
+ WORLD_OVERRIDE_FILE
}
- public LeafWorldConfig(File file, LeafWorldConfig defaults) throws Exception {
- this(file, defaults, null);
- }
+ private final Source source;
- public static LeafWorldConfig loadOverride(
- ConfigFile configFile,
- LeafWorldConfig defaults
- ) {
- return new LeafWorldConfig(configFile, defaults, null);
- }
+ public WorldConfigExample worldConfigExample = new WorldConfigExample();
+ public SaveFireworks saveFireworks = new SaveFireworks();
- private LeafWorldConfig(
- File file,
- @Nullable LeafWorldConfig defaults,
- LeafWorldConfig reloadSource
- ) throws Exception {
- super(file);
- this.defaults = defaults;
- this.reloadSource = reloadSource;
- loadModules();
- }
+ public boolean secureSeedEnabled;
- private LeafWorldConfig(
+ LeafWorldConfig(
ConfigFile configFile,
- @Nullable LeafWorldConfig defaults,
- LeafWorldConfig reloadSource
+ Source source
) {
super(configFile);
- this.defaults = defaults;
- this.reloadSource = reloadSource;
- loadModules();
- }
-
- private void loadModules() {
- try {
- ConfigModuleLoader.loadWorldModules(this);
- } finally {
- this.reloadSource = null;
- }
- }
-
- public static boolean exists(File file) {
- return file.isFile();
- }
-
- private boolean usesWorldDefaults(String path) {
- return this.defaults != null && !this.configFile.contains(path);
- }
-
- public boolean isDefaultsConfig() {
- return this.defaults == null;
- }
-
- boolean isReload() {
- return this.reloadSource != null;
- }
-
- /**
- * Returns this world's annotation-driven module instance.
- */
- public T getModule(Class moduleClass) {
- WorldConfigModule module = this.modules.get(moduleClass);
- if (module == null) {
- throw new IllegalArgumentException("World configuration module is not registered: "
- + moduleClass.getName());
- }
- return moduleClass.cast(module);
- }
-
- void registerModule(Class moduleClass, T module) {
- WorldConfigModule previousModule = this.modules.putIfAbsent(moduleClass, module);
- if (previousModule != null) {
- throw new IllegalStateException("Duplicate world configuration module: " + moduleClass.getName());
- }
- }
-
- T reloadModule(Class moduleClass) {
- if (this.reloadSource == null) {
- return null;
- }
- WorldConfigModule module = this.reloadSource.modules.get(moduleClass);
- return module == null ? null : moduleClass.cast(module);
- }
-
- @Override
- public boolean getBoolean(String path, boolean def, String comment) {
- return usesWorldDefaults(path) ? defaults.getBoolean(path, def, comment) : super.getBoolean(path, def, comment);
- }
-
- @Override
- public boolean getBoolean(String path, boolean def) {
- return usesWorldDefaults(path) ? defaults.getBoolean(path, def) : super.getBoolean(path, def);
- }
-
- @Override
- public String getString(String path, String def, String comment) {
- return usesWorldDefaults(path) ? defaults.getString(path, def, comment) : super.getString(path, def, comment);
- }
-
- @Override
- public String getString(String path, String def) {
- return usesWorldDefaults(path) ? defaults.getString(path, def) : super.getString(path, def);
- }
-
- @Override
- public double getDouble(String path, double def, String comment) {
- return usesWorldDefaults(path) ? defaults.getDouble(path, def, comment) : super.getDouble(path, def, comment);
- }
-
- @Override
- public double getDouble(String path, double def) {
- return usesWorldDefaults(path) ? defaults.getDouble(path, def) : super.getDouble(path, def);
- }
-
- @Override
- public int getInt(String path, int def, String comment) {
- return usesWorldDefaults(path) ? defaults.getInt(path, def, comment) : super.getInt(path, def, comment);
- }
-
- @Override
- public int getInt(String path, int def) {
- return usesWorldDefaults(path) ? defaults.getInt(path, def) : super.getInt(path, def);
- }
-
- @Override
- public long getLong(String path, long def, String comment) {
- return usesWorldDefaults(path) ? defaults.getLong(path, def, comment) : super.getLong(path, def, comment);
- }
-
- @Override
- public long getLong(String path, long def) {
- return usesWorldDefaults(path) ? defaults.getLong(path, def) : super.getLong(path, def);
- }
-
- @Override
- public List getList(String path, List def, String comment) {
- return usesWorldDefaults(path) ? defaults.getList(path, def, comment) : super.getList(path, def, comment);
- }
-
- @Override
- public List getList(String path, List def) {
- return usesWorldDefaults(path) ? defaults.getList(path, def) : super.getList(path, def);
- }
-
- @Override
- public ConfigSection getConfigSection(String path, Map values, String comment) {
- return usesWorldDefaults(path) ? defaults.getConfigSection(path, values, comment) : super.getConfigSection(path, values, comment);
- }
-
- @Override
- public ConfigSection getConfigSection(String path, Map values) {
- return usesWorldDefaults(path) ? defaults.getConfigSection(path, values) : super.getConfigSection(path, values);
- }
-
- @Override
- public Boolean getBoolean(String path) {
- return usesWorldDefaults(path) ? defaults.getBoolean(path) : super.getBoolean(path);
- }
-
- @Override
- public String getString(String path) {
- return usesWorldDefaults(path) ? defaults.getString(path) : super.getString(path);
+ this.source = source;
}
- @Override
- public Double getDouble(String path) {
- return usesWorldDefaults(path) ? defaults.getDouble(path) : super.getDouble(path);
+ public boolean isWorldDefaultsFile() {
+ return this.source == Source.WORLD_DEFAULTS_FILE;
}
-
- @Override
- public Integer getInt(String path) {
- return usesWorldDefaults(path) ? defaults.getInt(path) : super.getInt(path);
- }
-
- @Override
- public Long getLong(String path) {
- return usesWorldDefaults(path) ? defaults.getLong(path) : super.getLong(path);
- }
-
- @Override
- public List getList(String path) {
- return usesWorldDefaults(path) ? defaults.getList(path) : super.getList(path);
- }
-
- @Override
- public ConfigSection getConfigSection(String path) {
- return usesWorldDefaults(path) ? defaults.getConfigSection(path) : super.getConfigSection(path);
- }
-
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/WorldConfigModule.java b/leaf-server/src/main/java/org/dreeam/leaf/config/WorldConfigModule.java
index c5adce0071..377d52c439 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/WorldConfigModule.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/WorldConfigModule.java
@@ -3,7 +3,8 @@
/**
* Marker for a world-scoped Leaf configuration module.
*
- * Annotated fields must be mutable instance fields.
+ * Annotated fields must be mutable instance fields. Implementations must be exposed as typed
+ * fields on {@link LeafWorldConfig}.
*/
public interface WorldConfigModule {
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/migration/gale/GaleConfigMigration.java b/leaf-server/src/main/java/org/dreeam/leaf/config/migration/gale/GaleConfigMigration.java
index bf26ad2793..cda15eb289 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/migration/gale/GaleConfigMigration.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/migration/gale/GaleConfigMigration.java
@@ -4,6 +4,7 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dreeam.leaf.config.ConfigModule;
+import org.dreeam.leaf.config.LeafConfig;
import org.dreeam.leaf.config.LeafWorldConfig;
import org.dreeam.leaf.config.WorldConfigModule;
import org.dreeam.leaf.config.modules.gameplay.BookWriting;
@@ -84,7 +85,12 @@ public static void finalizeWorldDefaultsMigration() {
*
* @return the newly created Leaf override, or {@code null} when normal Leaf loading should continue
*/
- public static @Nullable LeafWorldConfig migrateWorldOverride(Path worldDirectory, File leafFile, LeafWorldConfig defaults) {
+ public static @Nullable LeafWorldConfig migrateWorldOverride(
+ Path worldDirectory,
+ File leafFile,
+ LeafWorldConfig defaults,
+ boolean reload
+ ) {
Path leafPath = leafFile.toPath();
Path galePath = worldDirectory.resolve(WORLD_OVERRIDE_FILE);
@@ -94,7 +100,7 @@ public static void finalizeWorldDefaultsMigration() {
boolean leafFileCreated = false;
try {
- if (LeafWorldConfig.exists(leafFile)) {
+ if (leafFile.isFile()) {
LOGGER.warn(
"Could not migrate Gale world config {} because Leaf world override {} already exists.",
galePath, leafPath
@@ -111,7 +117,7 @@ public static void finalizeWorldDefaultsMigration() {
leafFileCreated = true;
ConfigFile leafConfig = ConfigFile.loadConfig(leafFile);
applyMappings(galeConfig, resolvedWorldMappings, leafConfig);
- LeafWorldConfig migrated = LeafWorldConfig.loadOverride(leafConfig, defaults);
+ LeafWorldConfig migrated = LeafConfig.loadWorldOverride(leafConfig, defaults, reload);
migrated.saveConfig();
return migrated;
} catch (Exception exception) {
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/SecureSeed.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/SecureSeed.java
index 07fb9e6d8d..b4d1e51396 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/SecureSeed.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/SecureSeed.java
@@ -17,7 +17,7 @@ public SecureSeed() {
@Override
public void loadWorldConfig(LeafWorldConfig config) {
String path = "misc.secure-seed";
- if (config.isDefaultsConfig()) {
+ if (config.isWorldDefaultsFile()) {
config.addCommentRegionBased(path, """
Once you enable secure seed, all ores and structures are generated with a 1024-bit seed
instead of vanilla's 64-bit seed, making seed cracking impossible.""", """
From a92820dc031af2a6a9b473775a2afd2887200e17 Mon Sep 17 00:00:00 2001
From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com>
Date: Sat, 15 Aug 2026 14:24:21 +0800
Subject: [PATCH 10/14] Fixes
---
.../org/dreeam/leaf/config/ConfigBinder.java | 26 ++++
.../org/dreeam/leaf/config/LeafConfig.java | 139 ++++++++++++------
.../leaf/config/LeafConfigAccessor.java | 6 +-
.../dreeam/leaf/config/LeafWorldConfig.java | 12 +-
.../migration/gale/GaleConfigMigration.java | 41 +++---
5 files changed, 155 insertions(+), 69 deletions(-)
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigBinder.java b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigBinder.java
index 3b9ea96309..8783f178d2 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigBinder.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigBinder.java
@@ -120,6 +120,32 @@ private static void bindWorld(
}
}
+ static void applyWorldDefaults(
+ Object module,
+ Object defaultsModule,
+ boolean alreadyInitialized
+ ) throws IllegalAccessException {
+ Class> moduleClass = module.getClass();
+ if (defaultsModule.getClass() != moduleClass) {
+ throw new IllegalArgumentException("Configuration modules must have the same type");
+ }
+ if (alreadyInitialized && moduleClass.isAnnotationPresent(HotReloadUnsupported.class)) {
+ return;
+ }
+
+ for (Field field : moduleClass.getDeclaredFields()) {
+ if (field.getAnnotation(DoNotLoad.class) != null
+ || field.getAnnotation(ConfigInfo.class) == null
+ || alreadyInitialized && field.getAnnotation(HotReloadUnsupported.class) != null) {
+ continue;
+ }
+
+ validateField(moduleClass, field, false);
+ field.setAccessible(true);
+ field.set(module, copyValue(field.get(defaultsModule)));
+ }
+ }
+
// TODO[To-GitHub-issue]: Not sure whether needs to validate, we don't expose LeafConfig as public framework
private static void validateField(Class> moduleClass, Field field, boolean global) {
int modifiers = field.getModifiers();
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java
index f81c97c6cf..21cd38d4b3 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java
@@ -11,10 +11,9 @@
import org.apache.logging.log4j.Logger;
import org.dreeam.leaf.config.annotations.Experimental;
import org.dreeam.leaf.config.migration.LeafConfigMigration;
-import org.dreeam.leaf.config.modules.misc.SentryDSN;
import org.dreeam.leaf.config.migration.gale.GaleConfigMigration;
+import org.dreeam.leaf.config.modules.misc.SentryDSN;
import org.jspecify.annotations.NullMarked;
-import org.jspecify.annotations.Nullable;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.command.Command;
@@ -100,14 +99,51 @@ public static CompletableFuture reloadAsync(CommandSender sender) {
}, server);
}
- private static void reloadWorldConfig(MinecraftServer server) {
+ private static void reloadWorldConfig(MinecraftServer server) throws Exception {
for (ServerLevel level : server.getAllLevels()) {
Path worldDirectory = server.storageSource.getDimensionPath(level.dimension());
- LeafWorldConfig reloadedConfig = loadWorldConfig(worldDirectory, true);
- level.setLeafConfig(reloadedConfig);
+ LeafWorldConfig config = level.leafConfig();
+ reloadWorldConfig(config, worldDirectory);
+ level.setLeafConfig(config);
}
}
+ private static void reloadWorldConfig(
+ LeafWorldConfig config,
+ Path worldDirectory
+ ) throws Exception {
+ applyWorldConfig(config, worldDirectory, true);
+ }
+
+ private static LeafWorldConfig loadWorldConfig(Path worldDirectory) throws Exception {
+ LeafWorldConfig config = new LeafWorldConfig(
+ worldDefaultsConfig.configFile,
+ LeafWorldConfig.Source.WORLD_CONFIG
+ );
+ applyWorldConfig(config, worldDirectory, false);
+ return config;
+ }
+
+ private static void applyWorldConfig(
+ LeafWorldConfig config,
+ Path worldDirectory,
+ boolean alreadyInitialized
+ ) throws Exception {
+ applyWorldDefaults(config, worldDefaultsConfig, alreadyInitialized);
+
+ File worldConfigFile = worldDirectory.resolve(WORLD_CONFIG_FILE).toFile();
+ if (!worldConfigFile.isFile()) {
+ config.setConfigFile(worldDefaultsConfig.configFile);
+ return;
+ }
+ applyWorldOverride(
+ config,
+ ConfigFile.loadConfig(worldConfigFile),
+ worldDefaultsConfig,
+ alreadyInitialized
+ );
+ }
+
// Init config
public static void loadConfig() {
try {
@@ -147,12 +183,12 @@ private static void loadConfig(boolean init) throws Exception {
loadGlobalModules();
- worldDefaultsConfig = loadWorldDefaults(
- worldDefaultsFile,
- !init
- );
+ if (init) {
+ worldDefaultsConfig = loadWorldDefaults(worldDefaultsFile);
+ } else {
+ reloadWorldDefaults(worldDefaultsFile);
+ }
worldDefaultsConfig.saveConfig();
- GaleConfigMigration.finalizeWorldDefaultsMigration();
modulesInitialized = true;
}
@@ -165,37 +201,21 @@ public static LeafWorldConfig worldDefaultsConfig() {
}
/**
- * Loads an explicit world override without creating a file when the world uses the defaults.
+ * Creates a world configuration from the shared defaults and applies an existing override
+ * without creating a file when no override exists.
*/
public static LeafWorldConfig initWorldConfig(Path worldDirectory) {
File worldConfigFile = worldDirectory.resolve(WORLD_CONFIG_FILE).toFile();
LeafWorldConfig migratedConfig = GaleConfigMigration.migrateWorldOverride(
worldDirectory,
worldConfigFile,
- worldDefaultsConfig,
- false
+ worldDefaultsConfig
);
if (migratedConfig != null) {
return migratedConfig;
}
- if (!worldConfigFile.isFile()) {
- return worldDefaultsConfig;
- }
try {
- return loadWorldOverride(ConfigFile.loadConfig(worldConfigFile), worldDefaultsConfig, false);
- } catch (Exception exception) {
- throw new RuntimeException("Could not load Leaf world config for " + worldDirectory, exception);
- }
- }
-
- private static LeafWorldConfig loadWorldConfig(Path worldDirectory, boolean reload) {
- File worldConfigFile = worldDirectory.resolve(WORLD_CONFIG_FILE).toFile();
-
- if (!worldConfigFile.isFile()) {
- return worldDefaultsConfig;
- }
- try {
- return loadWorldOverride(ConfigFile.loadConfig(worldConfigFile), worldDefaultsConfig, reload);
+ return loadWorldConfig(worldDirectory);
} catch (Exception exception) {
throw new RuntimeException("Could not load Leaf world config for " + worldDirectory, exception);
}
@@ -241,7 +261,6 @@ private static void loadGlobalModules() throws ReflectiveOperationException {
for (ConfigModule module : GLOBAL_MODULES) {
ConfigBinder.bind(module, null, globalConfig, true, modulesInitialized);
module.onLoaded();
- GLOBAL_MODULES.add(module);
Class> moduleClass = module.getClass();
collectEnabledFields(moduleClass, enabledExperimentalModules, deprecatedModules);
@@ -257,39 +276,56 @@ private static void loadGlobalModules() throws ReflectiveOperationException {
);
}
- private static LeafWorldConfig loadWorldDefaults(
- ConfigFile configFile,
- boolean reload
- ) throws ReflectiveOperationException {
+ private static LeafWorldConfig loadWorldDefaults(ConfigFile configFile) throws ReflectiveOperationException {
if (WORLD_MODULES.isEmpty()) {
discoverWorldModules();
}
LeafWorldConfig config = new LeafWorldConfig(configFile, LeafWorldConfig.Source.WORLD_DEFAULTS_FILE);
+ applyWorldDefaultsFile(config, configFile, false);
+ return config;
+ }
+
+ private static void reloadWorldDefaults(ConfigFile configFile) throws ReflectiveOperationException {
+ applyWorldDefaultsFile(worldDefaultsConfig, configFile, true);
+ }
+
+ private static void applyWorldDefaultsFile(
+ LeafWorldConfig config,
+ ConfigFile configFile,
+ boolean alreadyInitialized
+ ) throws ReflectiveOperationException {
+ config.setConfigFile(configFile);
for (Field moduleField : WORLD_MODULES) {
WorldConfigModule module = (WorldConfigModule) moduleField.get(config);
- ConfigBinder.bind(module, null, config, false, reload);
+ ConfigBinder.bind(module, null, config, false, alreadyInitialized);
}
+ }
+ public static LeafWorldConfig loadWorldOverride(
+ ConfigFile configFile,
+ LeafWorldConfig worldDefaults
+ ) throws ReflectiveOperationException {
+ LeafWorldConfig config = new LeafWorldConfig(worldDefaults.configFile, LeafWorldConfig.Source.WORLD_CONFIG);
+ applyWorldDefaults(config, worldDefaults, false);
+ applyWorldOverride(config, configFile, worldDefaults, false);
return config;
}
- public static LeafWorldConfig loadWorldOverride(
+ private static void applyWorldOverride(
+ LeafWorldConfig config,
ConfigFile configFile,
LeafWorldConfig worldDefaults,
- boolean reload
+ boolean alreadyInitialized
) throws ReflectiveOperationException {
- LeafWorldConfig config = new LeafWorldConfig(configFile, LeafWorldConfig.Source.WORLD_OVERRIDE_FILE);
+ config.setConfigFile(configFile);
for (Field moduleField : WORLD_MODULES) {
WorldConfigModule module = (WorldConfigModule) moduleField.get(config);
-
WorldConfigModule worldDefaultModule = (WorldConfigModule) moduleField.get(worldDefaults);
- ConfigBinder.bind(module, worldDefaultModule, config, false, reload);
+ ConfigBinder.bind(module, worldDefaultModule, config, false, alreadyInitialized);
}
-
- return config;
}
public static void loadAfterBootstrap() {
@@ -299,7 +335,6 @@ public static void loadAfterBootstrap() {
try {
globalConfig.saveConfig();
- finalizeGlobalConfigMigration();
} catch (Exception exception) {
LOGGER.error("Failed to save config file!", exception);
}
@@ -337,8 +372,20 @@ private static void warnEnabledModules(List fields, String message) {
.toList());
}
- static void finalizeGlobalConfigMigration() {
- GaleConfigMigration.finalizeGlobalMigration();
+ public static void finalizeGaleConfigMigration(MinecraftServer server) {
+ GaleConfigMigration.finalizeMigration(server);
+ }
+
+ private static void applyWorldDefaults(
+ LeafWorldConfig config,
+ LeafWorldConfig defaults,
+ boolean alreadyInitialized
+ ) throws IllegalAccessException {
+ for (Field moduleField : WORLD_MODULES) {
+ WorldConfigModule module = (WorldConfigModule) moduleField.get(config);
+ WorldConfigModule defaultsModule = (WorldConfigModule) moduleField.get(defaults);
+ ConfigBinder.applyWorldDefaults(module, defaultsModule, alreadyInitialized);
+ }
}
static boolean isChineseLocale() {
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigAccessor.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigAccessor.java
index e18c8959c5..846eeca36b 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigAccessor.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigAccessor.java
@@ -12,12 +12,16 @@
/** Shared configuration-file utilities for global and world configuration views. */
abstract class LeafConfigAccessor {
- protected final ConfigFile configFile;
+ protected ConfigFile configFile;
protected LeafConfigAccessor(ConfigFile configFile) {
this.configFile = configFile;
}
+ void setConfigFile(ConfigFile configFile) {
+ this.configFile = configFile;
+ }
+
public void saveConfig() throws Exception {
configFile.save();
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafWorldConfig.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafWorldConfig.java
index 93c0c9d1c2..6ea50f19c6 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafWorldConfig.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafWorldConfig.java
@@ -5,17 +5,19 @@
import org.dreeam.leaf.config.modules.opt.SaveFireworks;
/**
- * An optional world-level overlay for {@link LeafConfig#worldDefaultsConfig()}.
+ * A world-level configuration initialized from {@link LeafConfig#worldDefaultsConfig()} with an
+ * optional per-world overlay.
*
- * The file is never created by this class. Worlds without {@code leaf-world.yml} use the
- * shared defaults directly. World modules are exposed as typed fields for direct access through
- * a level's Leaf configuration.
+ * The file is never created by this class. Every world receives its own configuration instance,
+ * initially inherits the shared defaults, and then applies values explicitly defined in
+ * {@code leaf-world.yml}. World modules are exposed as typed fields for direct access through a
+ * level's Leaf configuration.
*/
public final class LeafWorldConfig extends LeafConfigAccessor {
enum Source {
WORLD_DEFAULTS_FILE,
- WORLD_OVERRIDE_FILE
+ WORLD_CONFIG
}
private final Source source;
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/migration/gale/GaleConfigMigration.java b/leaf-server/src/main/java/org/dreeam/leaf/config/migration/gale/GaleConfigMigration.java
index cda15eb289..feac2349c8 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/migration/gale/GaleConfigMigration.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/migration/gale/GaleConfigMigration.java
@@ -72,12 +72,23 @@ private static void addWorldMapping(String oldPath, Class extends WorldConfigM
worldMappings.add(new Mapping(oldPath, moduleClass, fieldName));
}
- public static void finalizeGlobalMigration() {
- archive(configDirectory.resolve(GLOBAL_FILE));
- }
+ public static void finalizeMigration(MinecraftServer server) {
+ if (configDirectory == null) {
+ return;
+ }
+
+ boolean galeConfigFound = archive(configDirectory.resolve(GLOBAL_FILE));
+ galeConfigFound |= archive(configDirectory.resolve(WORLD_DEFAULTS_FILE));
+ for (ServerLevel level : server.getAllLevels()) {
+ Path worldDirectory = server.storageSource.getDimensionPath(level.dimension());
+ galeConfigFound |= archiveWorldOverride(worldDirectory.resolve(WORLD_OVERRIDE_FILE), worldDirectory);
+ }
- public static void finalizeWorldDefaultsMigration() {
- archive(configDirectory.resolve(WORLD_DEFAULTS_FILE));
+ if (galeConfigFound) {
+ LOGGER.warn(
+ "Gale configuration migration has finished. Please manually check the migrated Leaf configuration files for correctness."
+ );
+ }
}
/**
@@ -88,8 +99,7 @@ public static void finalizeWorldDefaultsMigration() {
public static @Nullable LeafWorldConfig migrateWorldOverride(
Path worldDirectory,
File leafFile,
- LeafWorldConfig defaults,
- boolean reload
+ LeafWorldConfig defaults
) {
Path leafPath = leafFile.toPath();
Path galePath = worldDirectory.resolve(WORLD_OVERRIDE_FILE);
@@ -117,7 +127,7 @@ public static void finalizeWorldDefaultsMigration() {
leafFileCreated = true;
ConfigFile leafConfig = ConfigFile.loadConfig(leafFile);
applyMappings(galeConfig, resolvedWorldMappings, leafConfig);
- LeafWorldConfig migrated = LeafConfig.loadWorldOverride(leafConfig, defaults, reload);
+ LeafWorldConfig migrated = LeafConfig.loadWorldOverride(leafConfig, defaults);
migrated.saveConfig();
return migrated;
} catch (Exception exception) {
@@ -133,12 +143,6 @@ public static void finalizeWorldDefaultsMigration() {
galePath, worldDirectory, exception
);
return null;
- } finally {
- archiveWorldOverride(galePath, worldDirectory);
- LOGGER.warn(
- "Finished processing Gale world config for {}. Please manually check the Leaf configuration used by this world.",
- worldDirectory
- );
}
}
@@ -202,17 +206,19 @@ private static Path worldContext(Path worldDirectory) {
}
}
- private static void archive(Path srcPath) {
- if (!Files.isRegularFile(srcPath)) return;
+ private static boolean archive(Path srcPath) {
+ if (!Files.isRegularFile(srcPath)) return false;
try {
Path backupPath = Path.of(srcPath.getFileName().toString());
moveToBackup(srcPath, backupPath);
} catch (IOException exception) {
LOGGER.error("Failed to back up Gale config {}; leaving it in place.", srcPath, exception);
}
+ return true;
}
- private static void archiveWorldOverride(Path srcPath, Path worldDirectory) {
+ private static boolean archiveWorldOverride(Path srcPath, Path worldDirectory) {
+ if (!Files.isRegularFile(srcPath)) return false;
try {
Path fileName = Path.of(srcPath.getFileName().toString());
Path backupPath = Path.of("world-overrides").resolve(worldContext(worldDirectory)).resolve(fileName);
@@ -220,6 +226,7 @@ private static void archiveWorldOverride(Path srcPath, Path worldDirectory) {
} catch (IOException exception) {
LOGGER.error("Failed to back up Gale config {}; leaving it in place.", srcPath, exception);
}
+ return true;
}
private static void moveToBackup(Path srcPath, Path backupPath) throws IOException {
From 5156db262df4a8969bba2211502914f23d3c7440 Mon Sep 17 00:00:00 2001
From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com>
Date: Sat, 15 Aug 2026 14:25:10 +0800
Subject: [PATCH 11/14] Remove gale config reload
---
.../leaf/command/subcommands/ReloadCommand.java | 15 ---------------
1 file changed, 15 deletions(-)
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/command/subcommands/ReloadCommand.java b/leaf-server/src/main/java/org/dreeam/leaf/command/subcommands/ReloadCommand.java
index 978e33bbca..7eb07ca5e0 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/command/subcommands/ReloadCommand.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/command/subcommands/ReloadCommand.java
@@ -2,13 +2,11 @@
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
-import net.minecraft.server.MinecraftServer;
import org.dreeam.leaf.command.LeafCommand;
import org.dreeam.leaf.command.PermissionedLeafSubcommand;
import org.dreeam.leaf.config.LeafConfig;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
-import org.bukkit.craftbukkit.CraftServer;
import org.bukkit.permissions.PermissionDefault;
public final class ReloadCommand extends PermissionedLeafSubcommand {
@@ -22,23 +20,10 @@ public ReloadCommand() {
@Override
public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
- this.doGaleReload(sender);
this.doLeafReload(sender);
return true;
}
- // Gale start - Gale commands - /gale reload command
- private void doGaleReload(final CommandSender sender) {
- Command.broadcastCommandMessage(sender, Component.text("Reloading Gale config...", NamedTextColor.GREEN));
-
- MinecraftServer server = ((CraftServer) sender.getServer()).getServer();
- server.galeConfigurations.reloadConfigs(server);
- server.server.reloadCount++;
-
- Command.broadcastCommandMessage(sender, Component.text("Gale config reload complete.", NamedTextColor.GREEN));
- }
- // Gale end - Gale commands - /gale reload command
-
private void doLeafReload(final CommandSender sender) {
Command.broadcastCommandMessage(sender, Component.text("Reloading Leaf config...", NamedTextColor.GREEN));
From b8504490635a1eae97ebf350ee8e2587963fc2e1 Mon Sep 17 00:00:00 2001
From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com>
Date: Mon, 17 Aug 2026 12:30:04 +0800
Subject: [PATCH 12/14] Update
---
.../features/0327-Leaf-config-changes.patch | 497 +++++++++++++++++-
.../features/0068-Leaf-config-changes.patch | 141 +++++
.../org/dreeam/leaf/config/ConfigBinder.java | 172 ++++--
.../org/dreeam/leaf/config/LeafConfig.java | 299 ++++++-----
.../leaf/config/LeafConfigAccessor.java | 4 +-
.../dreeam/leaf/config/LeafGlobalConfig.java | 7 +-
.../dreeam/leaf/config/LeafWorldConfig.java | 33 +-
.../migration/gale/GaleConfigMigration.java | 139 ++---
.../config/modules/fixes/world/Fixes.java | 19 +
.../gameplay/global/GameplayMechanics.java | 13 +
.../gameplay/world/EnderDragonRespawn.java | 13 +
...ideFlamesOnEntitiesWithFireResistance.java | 13 +
.../RandomStrollIntoNonTickingChunks.java | 13 +
.../modules/misc/GlobalConfigExample.java | 36 ++
.../leaf/config/modules/misc/SecureSeed.java | 2 +-
.../modules/misc/WorldConfigExample.java | 21 +
.../leaf/config/modules/misc/global/Chat.java | 19 +
.../misc/global/LastTickTimeInTpsCommand.java | 16 +
.../modules/misc/global/LogToConsole.java | 35 ++
.../misc/global/PluginLibraryLoader.java | 27 +
.../network/global/ChatOrderVerification.java | 13 +
.../modules/network/global/Keepalive.java | 13 +
.../PremiumAccountSlowLoginTimeout.java | 13 +
.../modules/opt/global/ReducedIntervals.java | 22 +
.../opt/world/EntityWakeUpDuration.java | 13 +
.../config/modules/opt/world/LoadChunks.java | 16 +
.../opt/world/MaxProjectileChunkLoads.java | 22 +
.../world/OptimizedSheepOffspringColor.java | 13 +
.../modules/opt/world/ReducedIntervals.java | 16 +
.../modules/opt/world/SaveFireworks.java | 13 +
.../dreeam/leaf/config/util/ConfigFileIO.java | 151 ++++++
31 files changed, 1565 insertions(+), 259 deletions(-)
create mode 100644 leaf-server/paper-patches/features/0068-Leaf-config-changes.patch
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/fixes/world/Fixes.java
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/global/GameplayMechanics.java
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/world/EnderDragonRespawn.java
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/world/HideFlamesOnEntitiesWithFireResistance.java
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/world/RandomStrollIntoNonTickingChunks.java
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/GlobalConfigExample.java
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/WorldConfigExample.java
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/global/Chat.java
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/global/LastTickTimeInTpsCommand.java
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/global/LogToConsole.java
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/global/PluginLibraryLoader.java
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/global/ChatOrderVerification.java
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/global/Keepalive.java
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/global/PremiumAccountSlowLoginTimeout.java
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/global/ReducedIntervals.java
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/world/EntityWakeUpDuration.java
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/world/LoadChunks.java
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/world/MaxProjectileChunkLoads.java
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/world/OptimizedSheepOffspringColor.java
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/world/ReducedIntervals.java
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/world/SaveFireworks.java
create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/util/ConfigFileIO.java
diff --git a/leaf-server/minecraft-patches/features/0327-Leaf-config-changes.patch b/leaf-server/minecraft-patches/features/0327-Leaf-config-changes.patch
index 120541851f..195437cc55 100644
--- a/leaf-server/minecraft-patches/features/0327-Leaf-config-changes.patch
+++ b/leaf-server/minecraft-patches/features/0327-Leaf-config-changes.patch
@@ -4,11 +4,155 @@ Date: Wed, 12 Oct 2022 10:42:15 -0400
Subject: [PATCH] Leaf config changes
+diff --git a/ca/spottedleaf/dataconverter/minecraft/versions/V4290.java b/ca/spottedleaf/dataconverter/minecraft/versions/V4290.java
+index e1b2447e3a8655b79deaef0163159e9681acb9a7..75c53c02dec32d8bc02e5a841e53319cd6df2d86 100644
+--- a/ca/spottedleaf/dataconverter/minecraft/versions/V4290.java
++++ b/ca/spottedleaf/dataconverter/minecraft/versions/V4290.java
+@@ -254,7 +254,7 @@ public final class V4290 {
+ return ret;
+ }
+ } catch (final JsonParseException ex) {
+- if (org.galemc.gale.configuration.GaleGlobalConfiguration.get().logToConsole.invalidLegacyTextComponent) LOGGER.error("Failed to convert json to nbt: " + unparsedJson, ex); // Leaf - Do not log invalid flatten text component parse
++ if (org.dreeam.leaf.config.modules.misc.global.LogToConsole.invalidLegacyTextComponent) LOGGER.error("Failed to convert json to nbt: " + unparsedJson, ex); // Leaf - Do not log invalid flatten text component parse
+ }
+
+ return null;
+diff --git a/io/papermc/paper/entity/activation/ActivationRange.java b/io/papermc/paper/entity/activation/ActivationRange.java
+index 4d5c38c0fb6d18026f84be5eccf87d78608decaa..c05103092c25b82e72cfe990969836cc32a66265 100644
+--- a/io/papermc/paper/entity/activation/ActivationRange.java
++++ b/io/papermc/paper/entity/activation/ActivationRange.java
+@@ -83,7 +83,7 @@ public final class ActivationRange {
+
+ // Gale start - variable entity wake-up duration
+ private static int getWakeUpDurationWithVariance(Entity entity, int wakeUpDuration) {
+- final double deviation = entity.level().galeConfig().gameplayMechanics.entityWakeUpDurationRatioStandardDeviation;
++ final double deviation = entity.level().leafConfig().entityWakeUpDuration.ratioStandardDeviation;
+ final org.dreeam.leaf.util.math.random.FasterRandomSource wakeUpDurationRandom = org.dreeam.leaf.util.math.random.FasterRandomSource.SHARED_INSTANCE;
+
+ if (deviation <= 0) {
+diff --git a/net/minecraft/network/chat/SignedMessageChain.java b/net/minecraft/network/chat/SignedMessageChain.java
+index 3aa85202ed8fc2f45ca37504834ad2dcc141507a..dfa19e677e554f5abf9e6a01314d18c9b343fa8e 100644
+--- a/net/minecraft/network/chat/SignedMessageChain.java
++++ b/net/minecraft/network/chat/SignedMessageChain.java
+@@ -49,7 +49,7 @@ public class SignedMessageChain {
+ throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.CHAIN_BROKEN);
+ }
+
+- if (org.galemc.gale.configuration.GaleGlobalConfiguration.get().misc.verifyChatOrder && body.timeStamp().isBefore(SignedMessageChain.this.lastTimeStamp)) { // Gale - Pufferfish - make chat order verification configurable
++ if (org.dreeam.leaf.config.modules.network.global.ChatOrderVerification.enabled && body.timeStamp().isBefore(SignedMessageChain.this.lastTimeStamp)) { // Gale - Pufferfish - make chat order verification configurable
+ this.setChainBroken();
+ throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.OUT_OF_ORDER_CHAT, org.bukkit.event.player.PlayerKickEvent.Cause.OUT_OF_ORDER_CHAT); // Paper - kick event causes
+ }
+@@ -61,7 +61,7 @@ public class SignedMessageChain {
+ throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.INVALID_SIGNATURE);
+ }
+
+- if (org.galemc.gale.configuration.GaleGlobalConfiguration.get().logToConsole.chat.expiredMessageWarning && unpacked.hasExpiredServer(Instant.now())) { // Gale - do not log expired message warnings
++ if (org.dreeam.leaf.config.modules.misc.global.Chat.expiredMessageWarning && unpacked.hasExpiredServer(Instant.now())) { // Gale - do not log expired message warnings
+ SignedMessageChain.LOGGER.warn("Received expired chat: '{}'. Is the client/server system time unsynchronized?", body.content());
+ }
+
+diff --git a/net/minecraft/server/Main.java b/net/minecraft/server/Main.java
+index cd117cafbe9c3fe6bcfda09abe8afc8bd6629b66..c8d0b0b0fbbc17cc7c904a6f637727b80bbd9317 100644
+--- a/net/minecraft/server/Main.java
++++ b/net/minecraft/server/Main.java
+@@ -129,7 +129,7 @@ public class Main {
+ Thread.ofPlatform().daemon().name("DataFixers init thread").start(DataFixers::getDataFixer);
+ // Paper end - Perf: Init DataConverter asynchronously
+ Util.startTimerHackThread();
+- org.dreeam.leaf.config.ConfigModule.loadAfterBootstrap(); // Leaf - Leaf config - post load
++ org.dreeam.leaf.config.LeafConfig.loadAfterBootstrap(); // Leaf - Leaf config - post load
+ Path settingsFile = Paths.get("server.properties");
+ DedicatedServerSettings settings = new DedicatedServerSettings(options); // CraftBukkit - CLI argument support
+ settings.forceSave();
+diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
+index 3083f7da4bd04db8724f8e457559bef4469d7675..a9be9cbf19448c7012fc562d1bebe42a013770c1 100644
+--- a/net/minecraft/server/MinecraftServer.java
++++ b/net/minecraft/server/MinecraftServer.java
+@@ -699,6 +699,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop server.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(server.storageSource.getDimensionPath(dimension), dimension.identifier(), spigotConfig, server.registryAccess(), gameRules)), // Paper - create paper world configs
-+ spigotConfig -> server.galeConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(server.storageSource.getDimensionPath(dimension), dimension.identifier(), spigotConfig, server.registryAccess(), gameRules)), // Gale - Gale configuration
-+ spigotConfig -> org.dreeam.leaf.config.LeafConfig.createWorldConfig(server.storageSource.getDimensionPath(dimension)), // Leaf - per-world configuration
++ spigotConfig -> new org.galemc.gale.configuration.GaleWorldConfiguration(spigotConfig, dimension.identifier()), // Gale - Gale configuration
++ spigotConfig -> org.dreeam.leaf.config.LeafConfig.initWorldConfig(server.storageSource.getDimensionPath(dimension)), // Leaf - per-world configuration
+ executor
+ );
this.weatherData = savedDataStorage.computeIfAbsent(WeatherData.TYPE);
this.weatherData.setLevel(this);
this.typeKey = typeKey;
+diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
+index 4030776c3d12f8e797720180e141322b5d2cc548..4e2562d81775b94e7b10f1b64fddde5a358f52ca 100644
+--- a/net/minecraft/server/level/ServerPlayer.java
++++ b/net/minecraft/server/level/ServerPlayer.java
+@@ -2403,7 +2403,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
+ public void crit(final Entity entity) {
+ // Gale start - MultiPaper - broadcast crit animations as the entity being critted
+ var level = this.level();
+- level.getChunkSource().sendToTrackingPlayersAndSelf(level.galeConfig().gameplayMechanics.fixes.broadcastCritAnimationsAsTheEntityBeingCritted ? entity : this, new ClientboundAnimatePacket(entity, ClientboundAnimatePacket.CRITICAL_HIT));
++ level.getChunkSource().sendToTrackingPlayersAndSelf(level.leafConfig().fixes.broadcastCritAnimationsAsTheEntityBeingCritted ? entity : this, new ClientboundAnimatePacket(entity, ClientboundAnimatePacket.CRITICAL_HIT));
+ // Gale end - MultiPaper - broadcast crit animations as the entity being critted
+ }
+
+@@ -2411,7 +2411,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
+ public void magicCrit(final Entity entity) {
+ // Gale start - MultiPaper - broadcast crit animations as the entity being critted
+ var level = this.level();
+- level.getChunkSource().sendToTrackingPlayersAndSelf(level.galeConfig().gameplayMechanics.fixes.broadcastCritAnimationsAsTheEntityBeingCritted ? entity : this, new ClientboundAnimatePacket(entity, ClientboundAnimatePacket.MAGIC_CRITICAL_HIT));
++ level.getChunkSource().sendToTrackingPlayersAndSelf(level.leafConfig().fixes.broadcastCritAnimationsAsTheEntityBeingCritted ? entity : this, new ClientboundAnimatePacket(entity, ClientboundAnimatePacket.MAGIC_CRITICAL_HIT));
+ // Gale end - MultiPaper - broadcast crit animations as the entity being critted
+ }
+
+diff --git a/net/minecraft/server/level/WorldGenRegion.java b/net/minecraft/server/level/WorldGenRegion.java
+index 1049e948496fcbab5f3d5789ec1e975692309a36..96594a636d6c871b007e671daf290684d743936f 100644
+--- a/net/minecraft/server/level/WorldGenRegion.java
++++ b/net/minecraft/server/level/WorldGenRegion.java
+@@ -327,7 +327,7 @@ public class WorldGenRegion implements WorldGenLevel {
+ int chunkX = SectionPos.blockToSectionCoord(pos.getX());
+ int chunkZ = SectionPos.blockToSectionCoord(pos.getZ());
+ // Paper start - Buffer OOB setBlock calls
+- if (org.galemc.gale.configuration.GaleGlobalConfiguration.get().logToConsole.setBlockInFarChunk && !hasSetFarWarned) { // Gale - Purpur - do not log setBlock in far chunks
++ if (org.dreeam.leaf.config.modules.misc.global.LogToConsole.setBlockInFarChunk && !hasSetFarWarned) { // Gale - Purpur - do not log setBlock in far chunks
+ Util.logAndPauseIfInIde(
+ "Detected setBlock in a far chunk ["
+ + chunkX
+diff --git a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
+index bfa522910ce7e9cb83668b69fa4c40daec8ab910..f02e51e1468fee39daf46138a8162b42d677cbbe 100644
+--- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
++++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
+@@ -115,7 +115,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
+ long now = System.nanoTime();
+ io.papermc.paper.util.KeepAlive.PendingKeepAlive pending = this.keepAlive.pendingKeepAlives.peek();
+ // Gale start - Purpur - send multiple keep-alive packets
+- if (org.galemc.gale.configuration.GaleGlobalConfiguration.get().misc.keepalive.sendMultiple) {
++ if (org.dreeam.leaf.config.modules.network.global.Keepalive.sendMultiple) {
+ if (this.keepAlivePending && !keepAlives.isEmpty() && keepAlives.contains(packet.getId())) {
+ int ping = (int) (Util.getMillis() - packet.getId());
+ int updatedLatency = (this.latency * 3 + ping) / 4;
+@@ -306,7 +306,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
+ Profiler.get().push("keepAlive");
+ long now = Util.getMillis();
+ // Gale start - Purpur - send multiple keep-alive packets
+- if (org.galemc.gale.configuration.GaleGlobalConfiguration.get().misc.keepalive.sendMultiple) {
++ if (org.dreeam.leaf.config.modules.network.global.Keepalive.sendMultiple) {
+ if (this.checkIfClosed(now)) {
+ long currTime = System.nanoTime();
+ if ((currTime - this.keepAlive.lastKeepAliveTx) >= java.util.concurrent.TimeUnit.SECONDS.toNanos(1L)) { // 1 second
+diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+index 43b2d4584ffb255bdb1ca0070b386d90e910d381..7fe9bf0b224b8c6117d80d81269d5ba8c7a85467 100644
+--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
++++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+@@ -1359,12 +1359,12 @@ public class ServerGamePacketListenerImpl
+
+ @Override
+ public void handleEditBook(final ServerboundEditBookPacket packet) {
+- // Gale start - Pufferfish - make book writing configurable
++ // Leaf start - Pufferfish - make book writing configurable
+ final org.bukkit.craftbukkit.entity.CraftPlayer craftPlayer = this.player.getBukkitEntity();
+- if (!(org.galemc.gale.configuration.GaleGlobalConfiguration.get().gameplayMechanics.enableBookWriting || craftPlayer.hasPermission(org.bukkit.craftbukkit.util.permissions.CraftDefaultPermissions.writeBooks) || craftPlayer.hasPermission("pufferfish.usebooks"))) {
++ if (!(org.dreeam.leaf.config.modules.gameplay.global.GameplayMechanics.enableBookWriting || craftPlayer.hasPermission(org.bukkit.craftbukkit.util.permissions.CraftDefaultPermissions.writeBooks) || craftPlayer.hasPermission("pufferfish.usebooks"))) {
+ return;
+ }
+- // Gale end - Pufferfish - make book writing configurable
++ // Leaf end - Pufferfish - make book writing configurable
+ // Paper start - Book size limits
+ final io.papermc.paper.configuration.type.number.IntOr.Disabled pageMax = io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.bookSize.pageMax;
+ if (!this.cserver.isPrimaryThread() && pageMax.enabled()) {
+@@ -2745,7 +2745,7 @@ public class ServerGamePacketListenerImpl
+ // CraftBukkit start
+ String rawMessage = message.signedContent();
+ if (rawMessage.isEmpty()) {
+- if (org.galemc.gale.configuration.GaleGlobalConfiguration.get().logToConsole.chat.emptyMessageWarning) LOGGER.warn("{} tried to send an empty message", this.player.getScoreboardName()); // Gale - do not log empty message warnings
++ if (org.dreeam.leaf.config.modules.misc.global.Chat.emptyMessageWarning) LOGGER.warn("{} tried to send an empty message", this.player.getScoreboardName()); // Gale - do not log empty message warnings
+ } else if (this.getCraftPlayer().isConversing()) {
+ final String conversationInput = rawMessage;
+ this.server.processQueue.add(() -> ServerGamePacketListenerImpl.this.getCraftPlayer().acceptConversationInput(conversationInput));
+diff --git a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
+index c439a5d93e3ee57bcf8b7276c4d290c1a0b04a52..5d7a0f0a42faa293cba0a771e90be218ca904f9f 100644
+--- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
++++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
+@@ -106,7 +106,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
+ public void tickTimeout() {
+ // Paper end - login cookie API
+ // Gale start - make slow login timeout configurable
+- int slowLoginTimeout = org.galemc.gale.configuration.GaleGlobalConfiguration.get().misc.premiumAccountSlowLoginTimeout;
++ int slowLoginTimeout = org.dreeam.leaf.config.modules.network.global.PremiumAccountSlowLoginTimeout.ticks;
+ if (this.tick++ >= (slowLoginTimeout < 1 ? MAX_TICKS_BEFORE_LOGIN : slowLoginTimeout)) {
+ // Gale end - make slow login timeout configurable
+ this.disconnectAsync(Component.translatable("multiplayer.disconnect.slow_login")); // Paper
+@@ -139,7 +139,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
+ public void disconnect(final Component component) {
+ try {
+ // Gale start - Pufferfish - do not log disconnections with null id
+- if (!org.galemc.gale.configuration.GaleGlobalConfiguration.get().logToConsole.nullIdDisconnections && this.authenticatedProfile != null && this.authenticatedProfile.id() == null) {
++ if (!org.dreeam.leaf.config.modules.misc.global.LogToConsole.nullIdDisconnections && this.authenticatedProfile != null && this.authenticatedProfile.id() == null) {
+ String reasonString = component.getString();
+ if ("Disconnected".equals(reasonString) || Component.translatable("multiplayer.disconnect.generic").getString().equals(reasonString)) {
+ return;
+diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
+index 8f451c9116d0e464d01e85449891c7270bb1b0d7..6ebd0a86a07ae0a21976614fe6d0084b11226a9d 100644
+--- a/net/minecraft/server/players/PlayerList.java
++++ b/net/minecraft/server/players/PlayerList.java
+@@ -450,7 +450,7 @@ public abstract class PlayerList {
+ }
+ // Paper end - Configurable player collision
+ org.purpurmc.purpur.task.BossBarTask.addToAll(player); // Purpur - Implement TPSBar
+- if (org.galemc.gale.configuration.GaleGlobalConfiguration.get().logToConsole.playerLoginLocations) { // Gale - JettPack - make logging login location configurable
++ if (org.dreeam.leaf.config.modules.misc.global.LogToConsole.playerLoginLocations) { // Gale - JettPack - make logging login location configurable
+ // CraftBukkit start - moved down
+ LOGGER.info(
+ "{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", // Paper - add world identifier
+@@ -1420,7 +1420,7 @@ public abstract class PlayerList {
+ public void broadcastChatMessage(final PlayerChatMessage message, final Predicate isFiltered, final @Nullable ServerPlayer senderPlayer, final ChatType.Bound chatType, final @Nullable Function unsignedFunction) {
+ // Paper end
+ boolean trusted = this.verifyChatTrusted(message);
+- this.server.logChatMessage((unsignedFunction == null ? message.decoratedContent() : unsignedFunction.apply(this.server.console)), chatType, trusted || !org.dreeam.leaf.config.modules.network.ChatMessageSignature.enabled || !org.galemc.gale.configuration.GaleGlobalConfiguration.get().logToConsole.chat.notSecureMarker ? null : "Not Secure"); // Paper // Gale - do not log Not Secure marker // Leaf - Mirai - Configurable chat message signatures
++ this.server.logChatMessage((unsignedFunction == null ? message.decoratedContent() : unsignedFunction.apply(this.server.console)), chatType, trusted || !org.dreeam.leaf.config.modules.network.ChatMessageSignature.enabled || !org.dreeam.leaf.config.modules.misc.global.Chat.notSecureMarker ? null : "Not Secure"); // Paper // Gale - do not log Not Secure marker // Leaf - Mirai - Configurable chat message signatures
+ OutgoingChatMessage tracked = OutgoingChatMessage.create(message);
+ boolean wasFullyFiltered = false;
+
+diff --git a/net/minecraft/stats/ServerRecipeBook.java b/net/minecraft/stats/ServerRecipeBook.java
+index 95a50f25455ec86f7662e9abd039be17609ed1e4..1ed76c10465bc14b5c50b8394992a48478d64d1b 100644
+--- a/net/minecraft/stats/ServerRecipeBook.java
++++ b/net/minecraft/stats/ServerRecipeBook.java
+@@ -110,7 +110,7 @@ public class ServerRecipeBook extends RecipeBook {
+ ) {
+ for (ResourceKey> recipe : recipes) {
+ if (!validator.test(recipe)) {
+- if (org.galemc.gale.configuration.GaleGlobalConfiguration.get().logToConsole.unrecognizedRecipes) LOGGER.error("Tried to load unrecognized recipe: {} removed now.", recipe); // Gale - Purpur - do not log unrecognized recipes
++ if (org.dreeam.leaf.config.modules.misc.global.LogToConsole.unrecognizedRecipes) LOGGER.error("Tried to load unrecognized recipe: {} removed now.", recipe); // Gale - Purpur - do not log unrecognized recipes
+ } else {
+ recipeAddingMethod.accept(recipe);
+ }
+diff --git a/net/minecraft/stats/ServerStatsCounter.java b/net/minecraft/stats/ServerStatsCounter.java
+index 1df6efebeda39097d75590c751ec635962b6e626..b9097219282a028e1c3ac02fb28b2bab4e7c3efa 100644
+--- a/net/minecraft/stats/ServerStatsCounter.java
++++ b/net/minecraft/stats/ServerStatsCounter.java
+@@ -127,7 +127,7 @@ public class ServerStatsCounter extends StatsCounter {
+ this.stats
+ .putAll(
+ STATS_CODEC.parse(data.get("stats").orElseEmptyMap())
+- .resultOrPartial(error -> {if (org.galemc.gale.configuration.GaleGlobalConfiguration.get().logToConsole.invalidStatistics) LOGGER.error("Failed to parse statistics for {}: {}", this.file, error);}) // Gale - EMC - do not log invalid statistics
++ .resultOrPartial(error -> {if (org.dreeam.leaf.config.modules.misc.global.LogToConsole.invalidStatistics) LOGGER.error("Failed to parse statistics for {}: {}", this.file, error);}) // Gale - EMC - do not log invalid statistics
+ .orElse(Map.of())
+ );
+ }
+diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
+index 650a382301321196b5f547f500869f2906311260..7df5dd2be9315c118e8feb1011a876bdfae918e3 100644
+--- a/net/minecraft/world/entity/Entity.java
++++ b/net/minecraft/world/entity/Entity.java
+@@ -1039,7 +1039,7 @@ public abstract class Entity
+ if (!this.level().isClientSide()) {
+ // Gale start - Slice - hide flames on entities with fire resistance
+ if (this instanceof net.minecraft.world.entity.LivingEntity livingEntity) {
+- this.setSharedFlagOnFire(this.remainingFireTicks > 0 && (!this.level.galeConfig().gameplayMechanics.hideFlamesOnEntitiesWithFireResistance || !livingEntity.hasEffect(net.minecraft.world.effect.MobEffects.FIRE_RESISTANCE)));
++ this.setSharedFlagOnFire(this.remainingFireTicks > 0 && (!this.level.leafConfig().hideFlamesOnEntitiesWithFireResistance.enabled || !livingEntity.hasEffect(net.minecraft.world.effect.MobEffects.FIRE_RESISTANCE)));
+ } else {
+ // Gale end - Slice - hide flames on entities with fire resistance
+ this.setSharedFlagOnFire(this.remainingFireTicks > 0);
+diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java
+index 14cf550c114df95fd43a5e002715c34e92be3c2a..e7b2ee8b275cb701490d865be72bdf0dd7164fa5 100644
+--- a/net/minecraft/world/entity/LivingEntity.java
++++ b/net/minecraft/world/entity/LivingEntity.java
+@@ -463,7 +463,7 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin
+ if (this.isAlive() && this.level() instanceof ServerLevel level) {
+ boolean isPlayer = this instanceof Player;
+ // Gale start - Pufferfish - reduce in wall checks
+- long checkStuckInWallInterval = this.level().galeConfig().smallOptimizations.reducedIntervals.checkStuckInWall;
++ long checkStuckInWallInterval = this.level().leafConfig().reducedIntervals.checkStuckInWall;
+ if ((checkStuckInWallInterval <= 1 || (tickCount % checkStuckInWallInterval == 0 && couldPossiblyBeHurt(1.0F))) && this.isInWall()) {
+ // Gale end - Pufferfish - reduce in wall checks
+ this.hurtServer(level, this.damageSources().inWall(), 1.0F);
+diff --git a/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java b/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java
+index 42c4ec0446ef5f78fba953fcaebae911de658aa4..36acbcd056b09c5d97a93e65fc77b4e266e6f41f 100644
+--- a/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java
++++ b/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java
+@@ -103,7 +103,7 @@ public class BehaviorUtils {
+ itemEntity.setDeltaMovement(throwVector);
+ // Gale start - EMC - reduce villager item re-pickup
+ if (thrower instanceof net.minecraft.world.entity.npc.villager.Villager) {
+- int repickupDelay = thrower.level().galeConfig().smallOptimizations.reducedIntervals.villagerItemRepickup;
++ int repickupDelay = thrower.level().leafConfig().reducedIntervals.villagerItemRepickup;
+
+ if (repickupDelay <= -1) {
+ itemEntity.setDefaultPickUpDelay();
+diff --git a/net/minecraft/world/entity/ai/goal/RandomStrollGoal.java b/net/minecraft/world/entity/ai/goal/RandomStrollGoal.java
+index bc8fdce935f26c4ddb5c1b7e95e6317816fb9e88..df5fcee117314cbccfab6c385cec1346c5e055de 100644
+--- a/net/minecraft/world/entity/ai/goal/RandomStrollGoal.java
++++ b/net/minecraft/world/entity/ai/goal/RandomStrollGoal.java
+@@ -50,7 +50,7 @@ public class RandomStrollGoal extends Goal {
+ }
+
+ Vec3 pos = this.getPosition();
+- if (pos == null || (!this.mob.level().galeConfig().gameplayMechanics.entitiesCanRandomStrollIntoNonTickingChunks && !((net.minecraft.server.level.ServerLevel) this.mob.level()).isPositionEntityTicking(net.minecraft.core.BlockPos.containing(pos)))) { // Gale - MultiPaper - prevent entities random strolling into non-ticking chunks
++ if (pos == null || (!this.mob.level().leafConfig().randomStrollIntoNonTickingChunks.enabled && !((net.minecraft.server.level.ServerLevel) this.mob.level()).isPositionEntityTicking(net.minecraft.core.BlockPos.containing(pos)))) { // Gale - MultiPaper - prevent entities random strolling into non-ticking chunks
+ return false;
+ }
+
+diff --git a/net/minecraft/world/entity/ai/goal/RangedBowAttackGoal.java b/net/minecraft/world/entity/ai/goal/RangedBowAttackGoal.java
+index 1b2edb6ebf41041bf1d3aabcfe8ea348c0dcd469..e364d82ac613d6bc5e3997537a8348538c1e0047 100644
+--- a/net/minecraft/world/entity/ai/goal/RangedBowAttackGoal.java
++++ b/net/minecraft/world/entity/ai/goal/RangedBowAttackGoal.java
+@@ -121,7 +121,7 @@ public class RangedBowAttackGoal extends Go
+ this.mob.lookAt(target, 30.0F, 30.0F);
+ // Gale start - Purpur - fix MC-121706
+ }
+- if (!hasStrafingTime || this.mob.level().galeConfig().gameplayMechanics.fixes.mc121706) {
++ if (!hasStrafingTime || this.mob.level().leafConfig().fixes.mc121706) {
+ // Gale end - Purpur - fix MC-121706
+ this.mob.getLookControl().setLookAt(target, 30.0F, 30.0F);
+ }
+diff --git a/net/minecraft/world/entity/ai/sensing/Sensing.java b/net/minecraft/world/entity/ai/sensing/Sensing.java
+index fea91f2b1738ee82cc2da356851e9a91fa262d55..3a891ff9d466b2597545b8d91098c9bf4a23a086 100644
+--- a/net/minecraft/world/entity/ai/sensing/Sensing.java
++++ b/net/minecraft/world/entity/ai/sensing/Sensing.java
+@@ -20,7 +20,7 @@ public class Sensing {
+ public Sensing(final Mob mob) {
+ this.mob = mob;
+ // Gale start - Petal - reduce line of sight updates - expiring entity id lists
+- int updateLineOfSightInterval = org.galemc.gale.configuration.GaleGlobalConfiguration.get().smallOptimizations.reducedIntervals.updateEntityLineOfSight;
++ int updateLineOfSightInterval = org.dreeam.leaf.config.modules.opt.global.ReducedIntervals.updateEntityLineOfSight;
+ if (updateLineOfSightInterval <= 1) {
+ this.expiring = null;
+ } else {
+diff --git a/net/minecraft/world/entity/animal/fish/WaterAnimal.java b/net/minecraft/world/entity/animal/fish/WaterAnimal.java
+index 9bbeb92efe6eae57114792578c935242b4fd753f..3d26752e6ed23b5a43dad89434b6e7558e7394d3 100644
+--- a/net/minecraft/world/entity/animal/fish/WaterAnimal.java
++++ b/net/minecraft/world/entity/animal/fish/WaterAnimal.java
+@@ -81,7 +81,7 @@ public abstract class WaterAnimal extends PathfinderMob {
+ minSpawnLevel = level.getMinecraftWorld().paperConfig().entities.spawning.wateranimalSpawnHeight.minimum.or(minSpawnLevel);
+ // Paper end - Make water animal spawn height configurable
+ // Gale start - Purpur - fix MC-238526
+- boolean shouldFixMC238526 = spawnReason == EntitySpawnReason.SPAWNER && level.getMinecraftWorld().galeConfig().gameplayMechanics.fixes.mc238526;
++ boolean shouldFixMC238526 = spawnReason == EntitySpawnReason.SPAWNER && level.getMinecraftWorld().leafConfig().fixes.mc238526;
+ boolean isAllowedHeight = pos.getY() >= minSpawnLevel && pos.getY() <= seaLevel;
+ return (shouldFixMC238526 || isAllowedHeight)
+ // Gale end - Purpur - fix MC-238526
+diff --git a/net/minecraft/world/entity/projectile/FireworkRocketEntity.java b/net/minecraft/world/entity/projectile/FireworkRocketEntity.java
+index 571da17706bf21ef955117e9e2bbda24ed144bba..8f232847b3944607c2afefd06f98fe1ecd9c1003 100644
+--- a/net/minecraft/world/entity/projectile/FireworkRocketEntity.java
++++ b/net/minecraft/world/entity/projectile/FireworkRocketEntity.java
+@@ -351,10 +351,10 @@ public class FireworkRocketEntity extends Projectile implements ItemSupplier {
+ return DoubleDoubleImmutablePair.of(dx, dz);
+ }
+
+- // Gale start - EMC - make saving fireworks configurable
++ // Leaf start - EMC - make saving fireworks configurable
+ @Override
+ public boolean shouldBeSaved() {
+- return this.level().galeConfig().smallOptimizations.saveFireworks && super.shouldBeSaved();
++ return this.level().leafConfig().saveFireworks.enabled && super.shouldBeSaved();
+ }
+- // Gale end - EMC - make saving fireworks configurable
++ // Leaf end - EMC - make saving fireworks configurable
+ }
+diff --git a/net/minecraft/world/entity/projectile/Projectile.java b/net/minecraft/world/entity/projectile/Projectile.java
+index 8f0113a7f74cc207fc9e3ebe487af58aa0e3424d..4446ed8a1a75315386886593e5113f8f76e3c041 100644
+--- a/net/minecraft/world/entity/projectile/Projectile.java
++++ b/net/minecraft/world/entity/projectile/Projectile.java
+@@ -72,19 +72,19 @@ public abstract class Projectile extends Entity implements TraceableEntity {
+ boolean isLoaded = ((net.minecraft.server.level.ServerChunkCache) this.level().getChunkSource()).getChunkAtIfLoadedImmediately(newX, newZ) != null;
+
+ if (!isLoaded) {
+- var maxProjectileChunkLoadsConfig = this.level().galeConfig().smallOptimizations.maxProjectileChunkLoads;
++ var maxProjectileChunkLoadsConfig = this.level().leafConfig().maxProjectileChunkLoads;
+ int maxChunkLoadsPerTick = maxProjectileChunkLoadsConfig.perTick;
+
+ if (maxChunkLoadsPerTick >= 0 && chunksLoadedThisTick > maxChunkLoadsPerTick) {
+ return;
+ }
+
+- int maxChunkLoadsPerProjectile = maxProjectileChunkLoadsConfig.perProjectile.max;
++ int maxChunkLoadsPerProjectile = maxProjectileChunkLoadsConfig.perProjectileMax;
+
+ if (maxChunkLoadsPerProjectile >= 0 && this.chunksLoadedByProjectile >= maxChunkLoadsPerProjectile) {
+- if (maxProjectileChunkLoadsConfig.perProjectile.removeFromWorldAfterReachLimit) {
++ if (maxProjectileChunkLoadsConfig.perProjectileRemoveFromWorldAfterReachLimit) {
+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); // Leaf - Fix Pufferfish and Purpur patches - Purpur
+- } else if (maxProjectileChunkLoadsConfig.perProjectile.resetMovementAfterReachLimit) {
++ } else if (maxProjectileChunkLoadsConfig.perProjectileResetMovementAfterReachLimit) {
+ this.setDeltaMovement(0, this.getDeltaMovement().y, 0);
+ }
+
+diff --git a/net/minecraft/world/item/DyeColor.java b/net/minecraft/world/item/DyeColor.java
+index 35b14be6dc363f1727133e985a49ef262da88373..77c6c009e65d2e1ca7f2821b82bca5b8a4edb702 100644
+--- a/net/minecraft/world/item/DyeColor.java
++++ b/net/minecraft/world/item/DyeColor.java
+@@ -212,7 +212,7 @@ public enum DyeColor implements StringRepresentable {
+
+ public static DyeColor getMixedColor(final ServerLevel level, final DyeColor dyeColor1, final DyeColor dyeColor2) {
+ // Gale start - carpet-fixes - optimize sheep offspring color
+- if (level.galeConfig().smallOptimizations.useOptimizedSheepOffspringColor) {
++ if (level.leafConfig().optimizedSheepOffspringColor.enabled) {
+ DyeColor col = properDye(dyeColor1, dyeColor2);
+ if (col == null) col = level.simpleRandom.nextBoolean() ? dyeColor1 : dyeColor2;
+ return col;
+diff --git a/net/minecraft/world/item/EndCrystalItem.java b/net/minecraft/world/item/EndCrystalItem.java
+index b2b8efad9da71fddb1a1011c05b7d6c7fe71bffb..1675a20199068972be743251feeb783e0cc823d9 100644
+--- a/net/minecraft/world/item/EndCrystalItem.java
++++ b/net/minecraft/world/item/EndCrystalItem.java
+@@ -52,7 +52,7 @@ public class EndCrystalItem extends Item {
+ // CraftBukkit end
+ level.addFreshEntity(crystal);
+ level.gameEvent(context.getPlayer(), GameEvent.ENTITY_PLACE, above);
+- if (level.galeConfig().gameplayMechanics.tryRespawnEnderDragonAfterEndCrystalPlace) { // Gale - Pufferfish - make ender dragon respawn attempt after placing end crystals configurable
++ if (level.leafConfig().enderDragonRespawn.tryAfterEndCrystalPlace) { // Gale - Pufferfish - make ender dragon respawn attempt after placing end crystals configurable
+ EnderDragonFight fight = serverLevel.getDragonFight();
+ if (fight != null) {
+ fight.tryRespawn(above); // Paper - Perf: Do crystal-portal proximity check before entity lookup
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
-index dcc783fe5aa1b4790db5873e22b32ea4517d68df..048f4d0f0ce0166059781835c1590ac7ed49f934 100644
+index dcc783fe5aa1b4790db5873e22b32ea4517d68df..435792ec17f7b1171825af22406a83fe21804452 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
-@@ -179,6 +179,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -179,6 +179,16 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
}
// Gale end - Gale configuration
+ // Leaf start - per-world configuration
-+ private final org.dreeam.leaf.config.LeafWorldConfig leafConfig;
++ private volatile org.dreeam.leaf.config.LeafWorldConfig leafConfig;
+ public org.dreeam.leaf.config.LeafWorldConfig leafConfig() {
+ return this.leafConfig;
+ }
++ public void setLeafConfig(org.dreeam.leaf.config.LeafWorldConfig leafConfig) {
++ this.leafConfig = leafConfig;
++ }
+ // Leaf end - per-world configuration
+
public final org.purpurmc.purpur.PurpurWorldConfig purpurConfig; // Purpur - Purpur config files
public final io.papermc.paper.redstone.RedstoneWireTurbo turbo; // Leaf - SparklyPaper - parallel world ticking - moved to world
public static @Nullable BlockPos lastPhysicsProblem; // Spigot
-@@ -895,6 +902,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -895,6 +905,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
io.papermc.paper.configuration.WorldConfiguration> paperWorldConfigCreator, // Paper - create paper world config
java.util.function.Function galeWorldConfigCreator, // Gale - Gale configuration
@@ -61,7 +527,7 @@ index dcc783fe5aa1b4790db5873e22b32ea4517d68df..048f4d0f0ce0166059781835c1590ac7
java.util.concurrent.Executor executor // Paper - Anti-Xray
) {
// Paper start - getblock optimisations - cache world height/sections
-@@ -911,6 +920,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -911,6 +923,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config
this.purpurConfig = new org.purpurmc.purpur.PurpurWorldConfig(bukkitName, environment, worldKey); // Purpur - Purpur config files
this.galeConfig = galeWorldConfigCreator.apply(this.spigotConfig); // Gale - Gale configuration
@@ -69,3 +535,16 @@ index dcc783fe5aa1b4790db5873e22b32ea4517d68df..048f4d0f0ce0166059781835c1590ac7
this.playerBreedingCooldowns = this.getNewBreedingCooldownCache(); // Purpur - Add adjustable breeding cooldown to config
this.generator = generator;
this.world = new CraftWorld((ServerLevel) this, worldKey, biomeProvider, environment);
+diff --git a/net/minecraft/world/level/levelgen/PhantomSpawner.java b/net/minecraft/world/level/levelgen/PhantomSpawner.java
+index ba9a21ab3188a8ff0681e7d368eb4d2161d2e2c7..a30bdbdfd77c31ce330bad6be956325654059e94 100644
+--- a/net/minecraft/world/level/levelgen/PhantomSpawner.java
++++ b/net/minecraft/world/level/levelgen/PhantomSpawner.java
+@@ -54,7 +54,7 @@ public class PhantomSpawner implements CustomSpawner {
+ .south(-10 + random.nextInt(21));
+ // Gale start - MultiPaper - don't load chunks to spawn phantoms
+ BlockState blockState;
+- if (level.galeConfig().smallOptimizations.loadChunks.toSpawnPhantoms) {
++ if (level.leafConfig().loadChunks.toSpawnPhantoms) {
+ blockState = level.getBlockState(spawnPos);
+ } else {
+ blockState = level.getBlockStateIfLoaded(spawnPos);
diff --git a/leaf-server/paper-patches/features/0068-Leaf-config-changes.patch b/leaf-server/paper-patches/features/0068-Leaf-config-changes.patch
new file mode 100644
index 0000000000..600dac7988
--- /dev/null
+++ b/leaf-server/paper-patches/features/0068-Leaf-config-changes.patch
@@ -0,0 +1,141 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com>
+Date: Sat, 15 Aug 2026 09:05:57 +0800
+Subject: [PATCH] Leaf config changes
+
+
+diff --git a/src/main/java/io/papermc/paper/adventure/ChatProcessor.java b/src/main/java/io/papermc/paper/adventure/ChatProcessor.java
+index 77ba1fb52b291a9b205cdf0e571c7de03fa24686..be5b329aa6c50eda9e574a97e9018c4f4ce35413 100644
+--- a/src/main/java/io/papermc/paper/adventure/ChatProcessor.java
++++ b/src/main/java/io/papermc/paper/adventure/ChatProcessor.java
+@@ -317,7 +317,7 @@ public final class ChatProcessor {
+
+ private void sendToServer(final ChatType.Bound chatType, final @Nullable Function msgFunction) {
+ final PlayerChatMessage toConsoleMessage = msgFunction == null ? ChatProcessor.this.message : ChatProcessor.this.message.withUnsignedContent(msgFunction.apply(ChatProcessor.this.server.console));
+- ChatProcessor.this.server.logChatMessage(toConsoleMessage.decoratedContent(), chatType, !org.dreeam.leaf.config.modules.network.ChatMessageSignature.enabled || !org.galemc.gale.configuration.GaleGlobalConfiguration.get().logToConsole.chat.notSecureMarker || ChatProcessor.this.server.getPlayerList().verifyChatTrusted(toConsoleMessage) ? null : "Not Secure"); // Gale - do not log Not Secure marker // Leaf - Mirai - Configurable chat message signatures
++ ChatProcessor.this.server.logChatMessage(toConsoleMessage.decoratedContent(), chatType, !org.dreeam.leaf.config.modules.network.ChatMessageSignature.enabled || !org.dreeam.leaf.config.modules.misc.global.Chat.notSecureMarker || ChatProcessor.this.server.getPlayerList().verifyChatTrusted(toConsoleMessage) ? null : "Not Secure"); // Gale - do not log Not Secure marker // Leaf - Mirai - Configurable chat message signatures
+ }
+ }
+
+diff --git a/src/main/java/io/papermc/paper/world/migration/LegacyCraftBukkitWorldMigration.java b/src/main/java/io/papermc/paper/world/migration/LegacyCraftBukkitWorldMigration.java
+index ba24fda2395f07cdb3d1c0c66811dfd4a5b81533..f8b8fb411a65b6b06f24aec1470eee1a71f1f433 100644
+--- a/src/main/java/io/papermc/paper/world/migration/LegacyCraftBukkitWorldMigration.java
++++ b/src/main/java/io/papermc/paper/world/migration/LegacyCraftBukkitWorldMigration.java
+@@ -83,6 +83,7 @@ final class LegacyCraftBukkitWorldMigration {
+ }
+
+ WorldMigrationSupport.migratePaperWorldConfig(this.sourceRoot, this.targetDimensionPath);
++ WorldMigrationSupport.migrateGaleWorldConfig(this.sourceRoot, this.targetDimensionPath); // Leaf - migrate legacy Gale world config
+
+ final var levelDataResult = WorldMigrationSupport.readFixedLevelData(sourceAccess);
+ if (levelDataResult.fatalError()) {
+@@ -163,7 +164,9 @@ final class LegacyCraftBukkitWorldMigration {
+ if (Files.isDirectory(root.resolve(LevelResource.DATA.id()))) {
+ return true;
+ }
+- if (Files.isRegularFile(root.resolve(WorldMigrationSupport.PAPER_WORLD_CONFIG)) || Files.isRegularFile(root.resolve(WorldMigrationSupport.LEGACY_UID_FILE_NAME))) {
++ if (Files.isRegularFile(root.resolve(WorldMigrationSupport.PAPER_WORLD_CONFIG))
++ || Files.isRegularFile(root.resolve(WorldMigrationSupport.GALE_WORLD_CONFIG)) // Leaf - preserve legacy Gale world config
++ || Files.isRegularFile(root.resolve(WorldMigrationSupport.LEGACY_UID_FILE_NAME))) {
+ return true;
+ }
+ for (final String directory : WorldMigrationSupport.DIMENSION_DIRECTORIES) {
+diff --git a/src/main/java/io/papermc/paper/world/migration/VanillaWorldMigration.java b/src/main/java/io/papermc/paper/world/migration/VanillaWorldMigration.java
+index 00ab529b50f3cd8b5e0b3256d500e00066ade762..18b5ee30d419e1c275eec67c24b6364914cc7437 100644
+--- a/src/main/java/io/papermc/paper/world/migration/VanillaWorldMigration.java
++++ b/src/main/java/io/papermc/paper/world/migration/VanillaWorldMigration.java
+@@ -46,6 +46,7 @@ final class VanillaWorldMigration {
+
+ if (rootOwnsThisWorld) {
+ WorldMigrationSupport.migratePaperWorldConfig(context.baseRoot(), context.targetDimensionPath());
++ WorldMigrationSupport.migrateGaleWorldConfig(context.baseRoot(), context.targetDimensionPath()); // Leaf - migrate legacy Gale world config
+ migrateLegacyWorldMetadata(context);
+ }
+
+diff --git a/src/main/java/io/papermc/paper/world/migration/WorldMigrationSupport.java b/src/main/java/io/papermc/paper/world/migration/WorldMigrationSupport.java
+index f961539a3e38b8fedc882aa8ee66766193ac4ccb..d3c47277decb82b6f7bd47663c10674481b0ff59 100644
+--- a/src/main/java/io/papermc/paper/world/migration/WorldMigrationSupport.java
++++ b/src/main/java/io/papermc/paper/world/migration/WorldMigrationSupport.java
+@@ -29,6 +29,7 @@ final class WorldMigrationSupport {
+ private static final Logger LOGGER = LogUtils.getClassLogger();
+ static final List DIMENSION_DIRECTORIES = List.of("region", "entities", "poi");
+ static final String PAPER_WORLD_CONFIG = "paper-world.yml";
++ static final String GALE_WORLD_CONFIG = "gale-world.yml";
+ static final String LEGACY_UID_FILE_NAME = "uid.dat";
+
+ private WorldMigrationSupport() {
+@@ -68,18 +69,26 @@ final class WorldMigrationSupport {
+ }
+
+ static void migratePaperWorldConfig(final Path sourceRoot, final Path targetDimensionPath) throws IOException {
+- final Path source = sourceRoot.resolve(PAPER_WORLD_CONFIG);
++ migrateWorldConfig(sourceRoot, targetDimensionPath, PAPER_WORLD_CONFIG, "Paper");
++ }
++
++ static void migrateGaleWorldConfig(final Path sourceRoot, final Path targetDimensionPath) throws IOException {
++ migrateWorldConfig(sourceRoot, targetDimensionPath, GALE_WORLD_CONFIG, "Gale");
++ }
++
++ private static void migrateWorldConfig(final Path sourceRoot, final Path targetDimensionPath, final String fileName, final String configName) throws IOException {
++ final Path source = sourceRoot.resolve(fileName);
+ if (!Files.isRegularFile(source)) {
+ return;
+ }
+
+- final Path target = targetDimensionPath.resolve(PAPER_WORLD_CONFIG);
++ final Path target = targetDimensionPath.resolve(fileName);
+ if (Files.exists(target)) {
+ return;
+ }
+
+ Files.createDirectories(target.getParent());
+- LOGGER.info("Migrating Paper world config from {} to {}", source, target);
++ LOGGER.info("Migrating {} world config from {} to {}", configName, source, target);
+ Files.move(source, target);
+ }
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+index 22e244d63d13dffc1efa091ac8853a4fb224a958..7eecca5538e48f238b3d1d956501532fefba6b21 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+@@ -1005,7 +1005,6 @@ public final class CraftServer implements Server {
+ org.spigotmc.SpigotConfig.init((File) this.console.options.valueOf("spigot-settings")); // Spigot
+ this.console.paperConfigurations.reloadConfigs(this.console);
+ org.purpurmc.purpur.PurpurConfig.init((File) console.options.valueOf("purpur-settings")); // Purpur - Purpur config files
+- this.console.galeConfigurations.reloadConfigs(this.console); // Gale - Gale configuration
+ for (ServerLevel world : this.console.getAllLevels()) {
+ // world.serverLevelData.setDifficulty(config.difficulty); // Paper - per level difficulty
+ world.setSpawnSettings(world.isSpawningMonsters()); // Paper - per level difficulty (from MinecraftServer#setDifficulty(ServerLevel, Difficulty, boolean))
+diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java b/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java
+index 0efca0e1de2d6e5d9bc33c46174973758c8cabb3..5bd6d0062f09a23bc8562ac313816143e2abc8a9 100644
+--- a/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java
++++ b/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java
+@@ -260,7 +260,7 @@ public final class CraftLegacy {
+ }
+
+ static {
+- if (org.galemc.gale.configuration.GaleGlobalConfiguration.get().logToConsole.legacyMaterialInitialization) LOGGER.warn("Initializing Legacy Material Support. Unless you have legacy plugins and/or data this is a bug!"); // Paper - Improve logging and errors; doesn't need to be an error // Gale - Purpur - do not log legacy Material initialization
++ if (org.dreeam.leaf.config.modules.misc.global.LogToConsole.legacyMaterialInitialization) LOGGER.warn("Initializing Legacy Material Support. Unless you have legacy plugins and/or data this is a bug!"); // Paper - Improve logging and errors; doesn't need to be an error // Gale - Purpur - do not log legacy Material initialization
+ if (MinecraftServer.getServer() != null && MinecraftServer.getServer().isDebugging()) {
+ new Exception().printStackTrace();
+ }
+diff --git a/src/main/java/org/spigotmc/TicksPerSecondCommand.java b/src/main/java/org/spigotmc/TicksPerSecondCommand.java
+index 5a6347c8a7909ab25c302fdc9fd1e8a615ebe488..db168a9bde8fa447d50edd25f97383e93807b7b1 100644
+--- a/src/main/java/org/spigotmc/TicksPerSecondCommand.java
++++ b/src/main/java/org/spigotmc/TicksPerSecondCommand.java
+@@ -51,13 +51,13 @@ public class TicksPerSecondCommand extends Command {
+ builder.append(Component.join(JoinConfiguration.commas(true), tpsAvg));
+ sender.sendMessage(builder.asComponent());
+ // Gale start - YAPFA - last tick time - in TPS command
+- if (org.galemc.gale.configuration.GaleGlobalConfiguration.get().misc.lastTickTimeInTpsCommand.enabled) {
++ if (org.dreeam.leaf.config.modules.misc.global.LastTickTimeInTpsCommand.enabled) {
+ long lastTickProperTime = net.minecraft.server.MinecraftServer.lastTickProperTime;
+ long lastTickOversleepTime = net.minecraft.server.MinecraftServer.lastTickOversleepTime;
+ var lastTickTimeMessage = text("Last tick: ")
+ .append(formatTickTimeDuration(lastTickProperTime, 44, 50, 51));
+
+- if (org.galemc.gale.configuration.GaleGlobalConfiguration.get().misc.lastTickTimeInTpsCommand.addOversleep) {
++ if (org.dreeam.leaf.config.modules.misc.global.LastTickTimeInTpsCommand.addOversleep) {
+ lastTickTimeMessage = lastTickTimeMessage.append(text(" self + "))
+ .append(formatTickTimeDuration(lastTickOversleepTime, Math.max(1, 51 - lastTickProperTime), Math.max(2, 52 - lastTickProperTime), Math.max(3, 53 - lastTickProperTime)))
+ .append(text(" oversleep = "))
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigBinder.java b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigBinder.java
index 8783f178d2..3cd0bbdd67 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigBinder.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigBinder.java
@@ -10,20 +10,109 @@
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
/**
* Binds annotation-driven module fields to a global or world configuration view.
*/
final class ConfigBinder {
+ // Static fields no longer expose their code defaults after the first successful bind.
+ private static final Map GLOBAL_DEFAULT_VALUES = new HashMap<>();
+
+ static void registerGlobalDefaults(Object module) throws IllegalAccessException {
+ Class> moduleClass = module.getClass();
+ for (Field field : moduleClass.getDeclaredFields()) {
+ if (field.getAnnotation(ConfigInfo.class) == null
+ || field.isAnnotationPresent(DoNotLoad.class)) {
+ continue;
+ }
+
+ validateField(moduleClass, field, true);
+ field.setAccessible(true);
+ Object defaultValue = field.get(null);
+ if (defaultValue == null) {
+ throw new IllegalStateException("Configuration field has a null default value: " + field);
+ }
+ GLOBAL_DEFAULT_VALUES.putIfAbsent(field, copyValue(defaultValue));
+ }
+ }
+
+ static void collectGlobalReload(
+ Object module,
+ LeafConfigAccessor config,
+ List pendingValues
+ ) throws IllegalAccessException {
+ Class> moduleClass = module.getClass();
+ ConfigClassInfo classInfo = moduleClass.getAnnotation(ConfigClassInfo.class);
+ if (classInfo == null) {
+ throw new IllegalStateException("Configuration module " + moduleClass.getName()
+ + " is missing @ConfigClassInfo");
+ }
+
+ String basePath = ConfigPaths.modulePath(moduleClass);
+ String sectionComment = config.pickStringRegionBased(classInfo.comments());
+ if (sectionComment != null) {
+ config.addComment(basePath, sectionComment);
+ }
+
+ boolean skipModuleReload = moduleClass.isAnnotationPresent(HotReloadUnsupported.class);
+
+ for (Field field : moduleClass.getDeclaredFields()) {
+ ConfigInfo configInfo = field.getAnnotation(ConfigInfo.class);
+ if (configInfo == null
+ || field.isAnnotationPresent(DoNotLoad.class)) {
+ continue;
+ }
+
+ validateField(moduleClass, field, true);
+ field.setAccessible(true);
+
+ String path = ConfigPaths.fieldPath(moduleClass, field);
+ Object defaultValue = globalDefaultValue(field);
+
+ String comment = config.pickStringRegionBased(configInfo.comments());
+ Object loadedValue = readValue(config, path, comment, field, defaultValue, true);
+ if (!skipModuleReload && !field.isAnnotationPresent(HotReloadUnsupported.class)) {
+ pendingValues.add(new PendingValue(null, field, loadedValue));
+ }
+ }
+ }
+
+ static void collectWorldReload(
+ Object module,
+ Object loadedModule,
+ List pendingValues
+ ) throws IllegalAccessException {
+ Class> moduleClass = module.getClass();
+ if (loadedModule.getClass() != moduleClass) {
+ throw new IllegalArgumentException("Configuration modules must have the same type");
+ }
+ if (moduleClass.isAnnotationPresent(HotReloadUnsupported.class)) {
+ return;
+ }
+
+ for (Field field : moduleClass.getDeclaredFields()) {
+ if (field.getAnnotation(ConfigInfo.class) == null
+ || field.isAnnotationPresent(DoNotLoad.class)
+ || field.isAnnotationPresent(HotReloadUnsupported.class)) {
+ continue;
+ }
+
+ validateField(moduleClass, field, false);
+ field.setAccessible(true);
+ pendingValues.add(new PendingValue(module, field, field.get(loadedModule)));
+ }
+ }
+
public static void bind(
Object module,
@Nullable Object worldDefaultModule,
LeafConfigAccessor config,
- boolean global,
- boolean alreadyInitialized
+ boolean global
) throws IllegalAccessException {
Class> moduleClass = module.getClass();
ConfigClassInfo classInfo = moduleClass.getAnnotation(ConfigClassInfo.class);
@@ -34,17 +123,12 @@ public static void bind(
String basePath = ConfigPaths.modulePath(moduleClass);
String sectionComment = config.pickStringRegionBased(classInfo.comments());
- if (sectionComment != null && (!(config instanceof LeafWorldConfig worldConfig) || worldConfig.isWorldDefaultsFile())) {
+ if (sectionComment != null && (!(config instanceof LeafWorldConfig worldConfig) || worldConfig.isWorldDefaults())) {
config.addComment(basePath, sectionComment);
}
- boolean skipModuleReload = alreadyInitialized
- && moduleClass.isAnnotationPresent(HotReloadUnsupported.class);
-
for (Field field : moduleClass.getDeclaredFields()) {
boolean skipLoad = field.getAnnotation(DoNotLoad.class) != null;
- boolean skipReload = skipModuleReload
- || alreadyInitialized && field.getAnnotation(HotReloadUnsupported.class) != null;
ConfigInfo configInfo = field.getAnnotation(ConfigInfo.class);
if (skipLoad || configInfo == null) {
@@ -52,9 +136,9 @@ public static void bind(
}
if (global) {
- bindGlobal(moduleClass, field, configInfo, config, skipReload);
+ bindGlobal(moduleClass, field, configInfo, config);
} else {
- bindWorld(module, worldDefaultModule, moduleClass, field, configInfo, config, skipReload);
+ bindWorld(module, worldDefaultModule, moduleClass, field, configInfo, config);
}
}
}
@@ -63,24 +147,17 @@ private static void bindGlobal(
Class> moduleClass,
Field field,
ConfigInfo configInfo,
- LeafConfigAccessor config,
- boolean skipReload
+ LeafConfigAccessor config
) throws IllegalAccessException {
validateField(moduleClass, field, true);
field.setAccessible(true);
String path = ConfigPaths.fieldPath(moduleClass, field);
- Object defaultValue = field.get(null);
- if (defaultValue == null) {
- throw new IllegalStateException("Configuration field has a null default value: " + field);
- }
+ Object defaultValue = globalDefaultValue(field);
String comment = config.pickStringRegionBased(configInfo.comments());
- // Always call readValue, to keep comments on reloading
Object loadedValue = readValue(config, path, comment, field, defaultValue, true);
- if (!skipReload) {
- field.set(null, loadedValue);
- }
+ field.set(null, loadedValue);
}
private static void bindWorld(
@@ -89,8 +166,7 @@ private static void bindWorld(
Class> moduleClass,
Field field,
ConfigInfo configInfo,
- LeafConfigAccessor config,
- boolean skipReload
+ LeafConfigAccessor config
) throws IllegalAccessException {
validateField(moduleClass, field, false);
field.setAccessible(true);
@@ -105,38 +181,27 @@ private static void bindWorld(
boolean worldOverridden = defaultsModule != null;
if (worldOverridden && !config.contains(path)) {
- if (!skipReload) {
- // Use world default if no override path defined
- field.set(module, defaultValue);
- }
+ // Use world default if no override path defined
+ field.set(module, defaultValue);
return;
}
String comment = config.pickStringRegionBased(configInfo.comments());
- // Always call readValue, to keep comments on reloading.
Object loadedValue = readValue(config, path, comment, field, defaultValue, !worldOverridden);
- if (!skipReload) {
- field.set(module, loadedValue);
- }
+ field.set(module, loadedValue);
}
static void applyWorldDefaults(
Object module,
- Object defaultsModule,
- boolean alreadyInitialized
+ Object defaultsModule
) throws IllegalAccessException {
Class> moduleClass = module.getClass();
if (defaultsModule.getClass() != moduleClass) {
throw new IllegalArgumentException("Configuration modules must have the same type");
}
- if (alreadyInitialized && moduleClass.isAnnotationPresent(HotReloadUnsupported.class)) {
- return;
- }
-
for (Field field : moduleClass.getDeclaredFields()) {
if (field.getAnnotation(DoNotLoad.class) != null
- || field.getAnnotation(ConfigInfo.class) == null
- || alreadyInitialized && field.getAnnotation(HotReloadUnsupported.class) != null) {
+ || field.getAnnotation(ConfigInfo.class) == null) {
continue;
}
@@ -243,4 +308,35 @@ private static Object copyValue(Object value) {
}
return value;
}
+
+ private static Object globalDefaultValue(Field field) {
+ Object defaultValue = GLOBAL_DEFAULT_VALUES.get(field);
+ if (defaultValue == null) {
+ throw new IllegalStateException("Code default was not registered for configuration field: " + field);
+ }
+ return copyValue(defaultValue);
+ }
+
+ static final class PendingValue {
+
+ private final @Nullable Object target;
+ private final Field field;
+ private final Object value;
+ private final Object previousValue;
+
+ private PendingValue(@Nullable Object target, Field field, Object value) throws IllegalAccessException {
+ this.target = target;
+ this.field = field;
+ this.value = copyValue(value);
+ this.previousValue = copyValue(field.get(target));
+ }
+
+ void apply() throws IllegalAccessException {
+ this.field.set(this.target, copyValue(this.value));
+ }
+
+ void restore() throws IllegalAccessException {
+ this.field.set(this.target, copyValue(this.previousValue));
+ }
+ }
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java
index 21cd38d4b3..333c23ee5c 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java
@@ -13,6 +13,7 @@
import org.dreeam.leaf.config.migration.LeafConfigMigration;
import org.dreeam.leaf.config.migration.gale.GaleConfigMigration;
import org.dreeam.leaf.config.modules.misc.SentryDSN;
+import org.dreeam.leaf.config.util.ConfigFileIO;
import org.jspecify.annotations.NullMarked;
import org.bukkit.Bukkit;
import org.bukkit.World;
@@ -73,7 +74,6 @@ public class LeafConfig {
private static final List GLOBAL_MODULES = new ArrayList<>();
private static final List WORLD_MODULES = new ArrayList<>();
- private static boolean modulesInitialized;
private static ConfigVersion previousConfigVersion = ConfigVersion.initial();
@@ -86,9 +86,32 @@ public static CompletableFuture reloadAsync(CommandSender sender) {
try {
long begin = System.nanoTime();
- loadConfig(false);
- reloadWorldConfig(server);
- loadAfterBootstrap();
+ createDirectory(CONFIG_DIRECTORY);
+
+ ConfigFile globalConfigFile = ConfigFileIO.load(new File(CONFIG_DIRECTORY, GLOBAL_CONFIG_FILE));
+ ConfigFile worldDefaultsFile = ConfigFileIO.load(new File(CONFIG_DIRECTORY, DEFAULT_WORLD_CONFIG_FILE));
+ LeafGlobalConfig loadedGlobalConfig = new LeafGlobalConfig(globalConfigFile, false);
+ LeafWorldConfig loadedWorldDefaults = loadWorldDefaults(worldDefaultsFile);
+
+ List pendingValues = new ArrayList<>();
+ if (GLOBAL_MODULES.isEmpty()) {
+ discoverGlobalModules();
+ }
+ for (ConfigModule module : GLOBAL_MODULES) {
+ ConfigBinder.collectGlobalReload(module, loadedGlobalConfig, pendingValues);
+ }
+ collectWorldReloadValues(worldDefaultsConfig, loadedWorldDefaults, pendingValues);
+
+ List worldReloads = new ArrayList<>();
+ for (ServerLevel level : server.getAllLevels()) {
+ Path worldDirectory = server.storageSource.getDimensionPath(level.dimension());
+ LeafWorldConfig loadedConfig = loadWorldConfig(worldDirectory, loadedWorldDefaults);
+ LeafWorldConfig currentConfig = level.leafConfig();
+ collectWorldReloadValues(currentConfig, loadedConfig, pendingValues);
+ worldReloads.add(new WorldReload(currentConfig, loadedConfig.configFile, currentConfig.configFile));
+ }
+
+ commitReload(loadedGlobalConfig, loadedWorldDefaults, worldReloads, pendingValues);
final String success = String.format("Successfully reloaded config in %sms.", (System.nanoTime() - begin) / 1_000_000);
Command.broadcastCommandMessage(sender, Component.text(success, NamedTextColor.GREEN));
@@ -99,49 +122,24 @@ public static CompletableFuture reloadAsync(CommandSender sender) {
}, server);
}
- private static void reloadWorldConfig(MinecraftServer server) throws Exception {
- for (ServerLevel level : server.getAllLevels()) {
- Path worldDirectory = server.storageSource.getDimensionPath(level.dimension());
- LeafWorldConfig config = level.leafConfig();
- reloadWorldConfig(config, worldDirectory);
- level.setLeafConfig(config);
- }
- }
-
- private static void reloadWorldConfig(
- LeafWorldConfig config,
- Path worldDirectory
- ) throws Exception {
- applyWorldConfig(config, worldDirectory, true);
- }
-
- private static LeafWorldConfig loadWorldConfig(Path worldDirectory) throws Exception {
+ private static LeafWorldConfig loadWorldConfig(Path worldDirectory, LeafWorldConfig defaults) throws Exception {
LeafWorldConfig config = new LeafWorldConfig(
- worldDefaultsConfig.configFile,
- LeafWorldConfig.Source.WORLD_CONFIG
+ defaults.configFile,
+ LeafWorldConfig.Source.WORLD_OVERRIDE
);
- applyWorldConfig(config, worldDirectory, false);
- return config;
- }
-
- private static void applyWorldConfig(
- LeafWorldConfig config,
- Path worldDirectory,
- boolean alreadyInitialized
- ) throws Exception {
- applyWorldDefaults(config, worldDefaultsConfig, alreadyInitialized);
+ applyWorldDefaults(config, defaults);
File worldConfigFile = worldDirectory.resolve(WORLD_CONFIG_FILE).toFile();
if (!worldConfigFile.isFile()) {
- config.setConfigFile(worldDefaultsConfig.configFile);
- return;
+ config.setConfigFile(defaults.configFile);
+ return config;
}
applyWorldOverride(
config,
- ConfigFile.loadConfig(worldConfigFile),
- worldDefaultsConfig,
- alreadyInitialized
+ ConfigFileIO.load(worldConfigFile),
+ defaults
);
+ return config;
}
// Init config
@@ -151,47 +149,39 @@ public static void loadConfig() {
LOGGER.info("Loading config...");
purgeOutdated();
- loadConfig(true);
-
- LOGGER.info("Successfully loaded config in {}ms.", (System.nanoTime() - begin) / 1_000_000);
- } catch (Exception e) {
- LOGGER.error("Failed to load config modules!", e);
- }
- }
+ createDirectory(CONFIG_DIRECTORY);
- /* Load Global Config */
-
- private static void loadConfig(boolean init) throws Exception {
- // Create config folder
- createDirectory(CONFIG_DIRECTORY);
+ ConfigFile globalConfigFile = ConfigFileIO.load(new File(CONFIG_DIRECTORY, GLOBAL_CONFIG_FILE));
+ ConfigFile worldDefaultsFile = ConfigFileIO.load(new File(CONFIG_DIRECTORY, DEFAULT_WORLD_CONFIG_FILE));
- ConfigFile globalConfigFile = ConfigFile.loadConfig(new File(CONFIG_DIRECTORY, GLOBAL_CONFIG_FILE));
- ConfigFile worldDefaultsFile = ConfigFile.loadConfig(new File(CONFIG_DIRECTORY, DEFAULT_WORLD_CONFIG_FILE));
-
- if (init) {
// Migrate the same raw config instances that will be bound and saved below.
LeafConfigMigration.migrate(globalConfigFile, worldDefaultsFile);
-
GaleConfigMigration.migrate(
CONFIG_DIRECTORY.toPath(),
globalConfigFile,
worldDefaultsFile
);
- }
-
- globalConfig = new LeafGlobalConfig(globalConfigFile);
- loadGlobalModules();
+ globalConfig = new LeafGlobalConfig(globalConfigFile, true);
+ if (GLOBAL_MODULES.isEmpty()) {
+ discoverGlobalModules();
+ }
+ for (ConfigModule module : GLOBAL_MODULES) {
+ ConfigBinder.bind(module, null, globalConfig, true);
+ }
+ runGlobalModuleCallbacks();
- if (init) {
worldDefaultsConfig = loadWorldDefaults(worldDefaultsFile);
- } else {
- reloadWorldDefaults(worldDefaultsFile);
+ worldDefaultsConfig.saveConfig();
+
+ LOGGER.info("Successfully loaded config in {}ms.", (System.nanoTime() - begin) / 1_000_000);
+ } catch (Exception e) {
+ LOGGER.error("Failed to load config modules!", e);
}
- worldDefaultsConfig.saveConfig();
- modulesInitialized = true;
}
+ /* Load Global Config */
+
public static LeafGlobalConfig globalConfig() {
return globalConfig;
}
@@ -215,7 +205,7 @@ public static LeafWorldConfig initWorldConfig(Path worldDirectory) {
return migratedConfig;
}
try {
- return loadWorldConfig(worldDirectory);
+ return loadWorldConfig(worldDirectory, worldDefaultsConfig);
} catch (Exception exception) {
throw new RuntimeException("Could not load Leaf world config for " + worldDirectory, exception);
}
@@ -235,35 +225,35 @@ private static void discoverGlobalModules() throws ReflectiveOperationException
}
ConfigModule module = (ConfigModule) moduleClass.getConstructor().newInstance();
+ ConfigBinder.registerGlobalDefaults(module);
GLOBAL_MODULES.add(module);
}
}
- private static void discoverWorldModules() {
- Field[] fields = LeafWorldConfig.class.getDeclaredFields();
- ObjectArrays.quickSort(fields, Comparator.comparing((Field field) -> field.getType().getSimpleName())
- .thenComparing(field -> field.getType().getName()));
- for (Field field : fields) {
- if (WorldConfigModule.class.isAssignableFrom(field.getType())) {
- WORLD_MODULES.add(field);
- }
- }
- }
-
- private static void loadGlobalModules() throws ReflectiveOperationException {
- if (GLOBAL_MODULES.isEmpty()) {
- discoverGlobalModules();
- }
-
+ private static void runGlobalModuleCallbacks() throws IllegalAccessException {
List enabledExperimentalModules = new ArrayList<>();
List deprecatedModules = new ArrayList<>();
for (ConfigModule module : GLOBAL_MODULES) {
- ConfigBinder.bind(module, null, globalConfig, true, modulesInitialized);
module.onLoaded();
Class> moduleClass = module.getClass();
- collectEnabledFields(moduleClass, enabledExperimentalModules, deprecatedModules);
+ for (Field field : moduleClass.getDeclaredFields()) {
+ boolean experimental = field.isAnnotationPresent(Experimental.class);
+ boolean deprecated = field.isAnnotationPresent(Deprecated.class);
+ if ((!experimental && !deprecated) || !Modifier.isStatic(field.getModifiers())) {
+ continue;
+ }
+ field.setAccessible(true);
+ if (field.get(null) instanceof Boolean enabled && enabled) {
+ if (experimental) {
+ enabledExperimentalModules.add(field);
+ }
+ if (deprecated) {
+ deprecatedModules.add(field);
+ }
+ }
+ }
}
warnEnabledModules(
@@ -276,62 +266,116 @@ private static void loadGlobalModules() throws ReflectiveOperationException {
);
}
- private static LeafWorldConfig loadWorldDefaults(ConfigFile configFile) throws ReflectiveOperationException {
- if (WORLD_MODULES.isEmpty()) {
- discoverWorldModules();
+ private static void collectWorldReloadValues(
+ LeafWorldConfig config,
+ LeafWorldConfig loadedConfig,
+ List pendingValues
+ ) throws IllegalAccessException {
+ for (Field moduleField : WORLD_MODULES) {
+ WorldConfigModule module = (WorldConfigModule) moduleField.get(config);
+ WorldConfigModule loadedModule = (WorldConfigModule) moduleField.get(loadedConfig);
+ ConfigBinder.collectWorldReload(module, loadedModule, pendingValues);
}
-
- LeafWorldConfig config = new LeafWorldConfig(configFile, LeafWorldConfig.Source.WORLD_DEFAULTS_FILE);
- applyWorldDefaultsFile(config, configFile, false);
- return config;
}
- private static void reloadWorldDefaults(ConfigFile configFile) throws ReflectiveOperationException {
- applyWorldDefaultsFile(worldDefaultsConfig, configFile, true);
+ private static void commitReload(
+ LeafGlobalConfig loadedGlobalConfig,
+ LeafWorldConfig loadedWorldDefaults,
+ List worldReloads,
+ List pendingValues
+ ) throws Exception {
+ LeafGlobalConfig previousGlobalConfig = globalConfig;
+ ConfigFile previousWorldDefaultsFile = worldDefaultsConfig.configFile;
+ int appliedValues = 0;
+
+ try {
+ globalConfig = loadedGlobalConfig;
+ worldDefaultsConfig.setConfigFile(loadedWorldDefaults.configFile);
+ for (WorldReload worldReload : worldReloads) {
+ worldReload.config().setConfigFile(worldReload.loadedConfigFile());
+ }
+ for (ConfigBinder.PendingValue pendingValue : pendingValues) {
+ pendingValue.apply();
+ appliedValues++;
+ }
+
+ runGlobalModuleCallbacks();
+ runAfterBootstrapCallbacks();
+ ConfigFileIO.saveAtomically(worldDefaultsConfig.configFile, globalConfig.configFile);
+ } catch (Exception exception) {
+ for (int index = appliedValues - 1; index >= 0; index--) {
+ try {
+ pendingValues.get(index).restore();
+ } catch (IllegalAccessException restoreException) {
+ exception.addSuppressed(restoreException);
+ }
+ }
+ globalConfig = previousGlobalConfig;
+ worldDefaultsConfig.setConfigFile(previousWorldDefaultsFile);
+ for (WorldReload worldReload : worldReloads) {
+ worldReload.config().setConfigFile(worldReload.previousConfigFile());
+ }
+
+ try {
+ runGlobalModuleCallbacks();
+ } catch (Exception callbackException) {
+ exception.addSuppressed(callbackException);
+ }
+ try {
+ runAfterBootstrapCallbacks();
+ } catch (Exception callbackException) {
+ exception.addSuppressed(callbackException);
+ }
+ throw exception;
+ }
}
- private static void applyWorldDefaultsFile(
- LeafWorldConfig config,
- ConfigFile configFile,
- boolean alreadyInitialized
- ) throws ReflectiveOperationException {
- config.setConfigFile(configFile);
+ private static LeafWorldConfig loadWorldDefaults(ConfigFile configFile) throws ReflectiveOperationException {
+ if (WORLD_MODULES.isEmpty()) {
+ Field[] fields = LeafWorldConfig.class.getDeclaredFields();
+ ObjectArrays.quickSort(fields, Comparator.comparing((Field field) -> field.getType().getSimpleName())
+ .thenComparing(field -> field.getType().getName()));
+ for (Field field : fields) {
+ if (WorldConfigModule.class.isAssignableFrom(field.getType())) {
+ WORLD_MODULES.add(field);
+ }
+ }
+ }
+ LeafWorldConfig config = new LeafWorldConfig(configFile, LeafWorldConfig.Source.WORLD_DEFAULTS);
for (Field moduleField : WORLD_MODULES) {
WorldConfigModule module = (WorldConfigModule) moduleField.get(config);
- ConfigBinder.bind(module, null, config, false, alreadyInitialized);
+ ConfigBinder.bind(module, null, config, false);
}
+ return config;
}
public static LeafWorldConfig loadWorldOverride(
ConfigFile configFile,
LeafWorldConfig worldDefaults
) throws ReflectiveOperationException {
- LeafWorldConfig config = new LeafWorldConfig(worldDefaults.configFile, LeafWorldConfig.Source.WORLD_CONFIG);
- applyWorldDefaults(config, worldDefaults, false);
- applyWorldOverride(config, configFile, worldDefaults, false);
+ LeafWorldConfig config = new LeafWorldConfig(worldDefaults.configFile, LeafWorldConfig.Source.WORLD_OVERRIDE);
+ applyWorldDefaults(config, worldDefaults);
+ applyWorldOverride(config, configFile, worldDefaults);
return config;
}
private static void applyWorldOverride(
LeafWorldConfig config,
ConfigFile configFile,
- LeafWorldConfig worldDefaults,
- boolean alreadyInitialized
+ LeafWorldConfig worldDefaults
) throws ReflectiveOperationException {
config.setConfigFile(configFile);
for (Field moduleField : WORLD_MODULES) {
WorldConfigModule module = (WorldConfigModule) moduleField.get(config);
WorldConfigModule worldDefaultModule = (WorldConfigModule) moduleField.get(worldDefaults);
- ConfigBinder.bind(module, worldDefaultModule, config, false, alreadyInitialized);
+ ConfigBinder.bind(module, worldDefaultModule, config, false);
}
}
public static void loadAfterBootstrap() {
- for (ConfigModule module : GLOBAL_MODULES) {
- module.onRegistriesLoaded();
- }
+ runAfterBootstrapCallbacks();
try {
globalConfig.saveConfig();
@@ -340,26 +384,9 @@ public static void loadAfterBootstrap() {
}
}
- private static void collectEnabledFields(
- Class> moduleClass,
- List enabledExperimentalFields,
- List enabledDeprecatedFields
- ) throws IllegalAccessException {
- for (Field field : moduleClass.getDeclaredFields()) {
- boolean experimental = field.isAnnotationPresent(Experimental.class);
- boolean deprecated = field.isAnnotationPresent(Deprecated.class);
- if ((!experimental && !deprecated) || !Modifier.isStatic(field.getModifiers())) {
- continue;
- }
- field.setAccessible(true);
- if (field.get(null) instanceof Boolean enabled && enabled) {
- if (experimental) {
- enabledExperimentalFields.add(field);
- }
- if (deprecated) {
- enabledDeprecatedFields.add(field);
- }
- }
+ private static void runAfterBootstrapCallbacks() {
+ for (ConfigModule module : GLOBAL_MODULES) {
+ module.onRegistriesLoaded();
}
}
@@ -372,19 +399,14 @@ private static void warnEnabledModules(List fields, String message) {
.toList());
}
- public static void finalizeGaleConfigMigration(MinecraftServer server) {
- GaleConfigMigration.finalizeMigration(server);
- }
-
private static void applyWorldDefaults(
LeafWorldConfig config,
- LeafWorldConfig defaults,
- boolean alreadyInitialized
+ LeafWorldConfig defaults
) throws IllegalAccessException {
for (Field moduleField : WORLD_MODULES) {
WorldConfigModule module = (WorldConfigModule) moduleField.get(config);
WorldConfigModule defaultsModule = (WorldConfigModule) moduleField.get(defaults);
- ConfigBinder.applyWorldDefaults(module, defaultsModule, alreadyInitialized);
+ ConfigBinder.applyWorldDefaults(module, defaultsModule);
}
}
@@ -543,6 +565,13 @@ private static ConfigVersion parseStoredConfigVersion(String version, boolean wa
}
}
+ private record WorldReload(
+ LeafWorldConfig config,
+ ConfigFile loadedConfigFile,
+ ConfigFile previousConfigFile
+ ) {
+ }
+
private record ConfigVersion(List components) implements Comparable {
private static ConfigVersion initial() {
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigAccessor.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigAccessor.java
index 846eeca36b..9bd387fc26 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigAccessor.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigAccessor.java
@@ -3,6 +3,7 @@
import io.github.thatsmusic99.configurationmaster.api.ConfigFile;
import io.github.thatsmusic99.configurationmaster.api.ConfigSection;
import org.dreeam.leaf.config.migration.ConfigPathMigration;
+import org.dreeam.leaf.config.util.ConfigFileIO;
import org.jspecify.annotations.Nullable;
import java.io.File;
@@ -23,7 +24,7 @@ void setConfigFile(ConfigFile configFile) {
}
public void saveConfig() throws Exception {
- configFile.save();
+ ConfigFileIO.saveAtomically(configFile);
}
boolean contains(String path) {
@@ -181,6 +182,7 @@ public void addCommentRegionBased(String path, String en, String cn) {
configFile.addComment(path, LeafConfig.isChineseLocale() ? cn : en);
}
+ // TODO - why so complicated?
public @Nullable String pickStringRegionBased(String... localizedStrings) {
if (localizedStrings == null || localizedStrings.length == 0) return null;
if (localizedStrings.length == 1) return localizedStrings[0];
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafGlobalConfig.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafGlobalConfig.java
index 91017746ad..67b91b29ba 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafGlobalConfig.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafGlobalConfig.java
@@ -7,11 +7,12 @@
/** The server-wide Leaf configuration. */
public final class LeafGlobalConfig extends LeafConfigAccessor {
- LeafGlobalConfig(ConfigFile configFile) {
+ LeafGlobalConfig(ConfigFile configFile, boolean loadPreviousVersion) {
super(configFile);
- LeafConfig.loadPreviousConfigVersion(getString("config-version"));
-
+ if (loadPreviousVersion) {
+ LeafConfig.loadPreviousConfigVersion(getString("config-version"));
+ }
configFile.set("config-version", LeafConfig.CURRENT_CONFIG_VERSION);
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafWorldConfig.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafWorldConfig.java
index 6ea50f19c6..b2c6514ffa 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafWorldConfig.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafWorldConfig.java
@@ -1,8 +1,17 @@
package org.dreeam.leaf.config;
import io.github.thatsmusic99.configurationmaster.api.ConfigFile;
+import org.dreeam.leaf.config.modules.fixes.world.Fixes;
+import org.dreeam.leaf.config.modules.gameplay.world.EnderDragonRespawn;
+import org.dreeam.leaf.config.modules.gameplay.world.HideFlamesOnEntitiesWithFireResistance;
+import org.dreeam.leaf.config.modules.gameplay.world.RandomStrollIntoNonTickingChunks;
import org.dreeam.leaf.config.modules.misc.WorldConfigExample;
-import org.dreeam.leaf.config.modules.opt.SaveFireworks;
+import org.dreeam.leaf.config.modules.opt.world.EntityWakeUpDuration;
+import org.dreeam.leaf.config.modules.opt.world.LoadChunks;
+import org.dreeam.leaf.config.modules.opt.world.MaxProjectileChunkLoads;
+import org.dreeam.leaf.config.modules.opt.world.OptimizedSheepOffspringColor;
+import org.dreeam.leaf.config.modules.opt.world.ReducedIntervals;
+import org.dreeam.leaf.config.modules.opt.world.SaveFireworks;
/**
* A world-level configuration initialized from {@link LeafConfig#worldDefaultsConfig()} with an
@@ -16,26 +25,32 @@
public final class LeafWorldConfig extends LeafConfigAccessor {
enum Source {
- WORLD_DEFAULTS_FILE,
- WORLD_CONFIG
+ WORLD_DEFAULTS,
+ WORLD_OVERRIDE
}
private final Source source;
public WorldConfigExample worldConfigExample = new WorldConfigExample();
public SaveFireworks saveFireworks = new SaveFireworks();
+ public OptimizedSheepOffspringColor optimizedSheepOffspringColor = new OptimizedSheepOffspringColor();
+ public MaxProjectileChunkLoads maxProjectileChunkLoads = new MaxProjectileChunkLoads();
+ public ReducedIntervals reducedIntervals = new ReducedIntervals();
+ public LoadChunks loadChunks = new LoadChunks();
+ public Fixes fixes = new Fixes();
+ public RandomStrollIntoNonTickingChunks randomStrollIntoNonTickingChunks = new RandomStrollIntoNonTickingChunks();
+ public EntityWakeUpDuration entityWakeUpDuration = new EntityWakeUpDuration();
+ public HideFlamesOnEntitiesWithFireResistance hideFlamesOnEntitiesWithFireResistance = new HideFlamesOnEntitiesWithFireResistance();
+ public EnderDragonRespawn enderDragonRespawn = new EnderDragonRespawn();
public boolean secureSeedEnabled;
- LeafWorldConfig(
- ConfigFile configFile,
- Source source
- ) {
+ LeafWorldConfig(ConfigFile configFile, Source source) {
super(configFile);
this.source = source;
}
- public boolean isWorldDefaultsFile() {
- return this.source == Source.WORLD_DEFAULTS_FILE;
+ public boolean isWorldDefaults() {
+ return this.source == Source.WORLD_DEFAULTS;
}
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/migration/gale/GaleConfigMigration.java b/leaf-server/src/main/java/org/dreeam/leaf/config/migration/gale/GaleConfigMigration.java
index feac2349c8..989011a0dc 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/migration/gale/GaleConfigMigration.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/migration/gale/GaleConfigMigration.java
@@ -1,14 +1,15 @@
package org.dreeam.leaf.config.migration.gale;
import io.github.thatsmusic99.configurationmaster.api.ConfigFile;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ServerLevel;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dreeam.leaf.config.ConfigModule;
import org.dreeam.leaf.config.LeafConfig;
import org.dreeam.leaf.config.LeafWorldConfig;
import org.dreeam.leaf.config.WorldConfigModule;
-import org.dreeam.leaf.config.modules.gameplay.BookWriting;
-import org.dreeam.leaf.config.modules.opt.SaveFireworks;
+import org.dreeam.leaf.config.util.ConfigFileIO;
import org.dreeam.leaf.config.util.ConfigPaths;
import org.jspecify.annotations.Nullable;
@@ -59,9 +60,46 @@ private static void registerMappings() {
globalMappings = new ArrayList<>();
worldMappings = new ArrayList<>();
- addGlobalMapping("gameplay-mechanics.enable-book-writing", BookWriting.class, "enabled");
-
- addWorldMapping("small-optimizations.save-fireworks", SaveFireworks.class, "enabled");
+ addGlobalMapping("small-optimizations.reduced-intervals.increase-time-statistics", org.dreeam.leaf.config.modules.opt.global.ReducedIntervals.class, "increaseTimeStatistics");
+ addGlobalMapping("small-optimizations.reduced-intervals.update-entity-line-of-sight", org.dreeam.leaf.config.modules.opt.global.ReducedIntervals.class, "updateEntityLineOfSight");
+ addGlobalMapping("gameplay-mechanics.enable-book-writing", org.dreeam.leaf.config.modules.gameplay.global.GameplayMechanics.class, "enableBookWriting");
+ addGlobalMapping("misc.verify-chat-order", org.dreeam.leaf.config.modules.network.global.ChatOrderVerification.class, "enabled");
+ addGlobalMapping("misc.premium-account-slow-login-timeout", org.dreeam.leaf.config.modules.network.global.PremiumAccountSlowLoginTimeout.class, "ticks");
+ addGlobalMapping("misc.keepalive.send-multiple", org.dreeam.leaf.config.modules.network.global.Keepalive.class, "sendMultiple");
+ addGlobalMapping("misc.last-tick-time-in-tps-command.enabled", org.dreeam.leaf.config.modules.misc.global.LastTickTimeInTpsCommand.class, "enabled");
+ addGlobalMapping("misc.last-tick-time-in-tps-command.add-oversleep", org.dreeam.leaf.config.modules.misc.global.LastTickTimeInTpsCommand.class, "addOversleep");
+ addGlobalMapping("log-to-console.invalid-statistics", org.dreeam.leaf.config.modules.misc.global.LogToConsole.class, "invalidStatistics");
+ addGlobalMapping("log-to-console.ignored-advancements", org.dreeam.leaf.config.modules.misc.global.LogToConsole.class, "ignoredAdvancements");
+ addGlobalMapping("log-to-console.set-block-in-far-chunk", org.dreeam.leaf.config.modules.misc.global.LogToConsole.class, "setBlockInFarChunk");
+ addGlobalMapping("log-to-console.unrecognized-recipes", org.dreeam.leaf.config.modules.misc.global.LogToConsole.class, "unrecognizedRecipes");
+ addGlobalMapping("log-to-console.legacy-material-initialization", org.dreeam.leaf.config.modules.misc.global.LogToConsole.class, "legacyMaterialInitialization");
+ addGlobalMapping("log-to-console.null-id-disconnections", org.dreeam.leaf.config.modules.misc.global.LogToConsole.class, "nullIdDisconnections");
+ addGlobalMapping("log-to-console.player-login-locations", org.dreeam.leaf.config.modules.misc.global.LogToConsole.class, "playerLoginLocations");
+ addGlobalMapping("log-to-console.invalid-legacy-text-component", org.dreeam.leaf.config.modules.misc.global.LogToConsole.class, "invalidLegacyTextComponent");
+ addGlobalMapping("log-to-console.chat.empty-message-warning", org.dreeam.leaf.config.modules.misc.global.Chat.class, "emptyMessageWarning");
+ addGlobalMapping("log-to-console.chat.expired-message-warning", org.dreeam.leaf.config.modules.misc.global.Chat.class, "expiredMessageWarning");
+ addGlobalMapping("log-to-console.chat.not-secure-marker", org.dreeam.leaf.config.modules.misc.global.Chat.class, "notSecureMarker");
+ addGlobalMapping("log-to-console.plugin-library-loader.downloads", org.dreeam.leaf.config.modules.misc.global.PluginLibraryLoader.class, "downloads");
+ addGlobalMapping("log-to-console.plugin-library-loader.start-load-libraries-for-plugin", org.dreeam.leaf.config.modules.misc.global.PluginLibraryLoader.class, "startLoadLibrariesForPlugin");
+ addGlobalMapping("log-to-console.plugin-library-loader.library-loaded", org.dreeam.leaf.config.modules.misc.global.PluginLibraryLoader.class, "libraryLoaded");
+
+ addWorldMapping("small-optimizations.save-fireworks", org.dreeam.leaf.config.modules.opt.world.SaveFireworks.class, "enabled");
+ addWorldMapping("small-optimizations.use-optimized-sheep-offspring-color", org.dreeam.leaf.config.modules.opt.world.OptimizedSheepOffspringColor.class, "enabled");
+ addWorldMapping("small-optimizations.max-projectile-chunk-loads.per-tick", org.dreeam.leaf.config.modules.opt.world.MaxProjectileChunkLoads.class, "perTick");
+ addWorldMapping("small-optimizations.max-projectile-chunk-loads.per-projectile.max", org.dreeam.leaf.config.modules.opt.world.MaxProjectileChunkLoads.class, "perProjectileMax");
+ addWorldMapping("small-optimizations.max-projectile-chunk-loads.per-projectile.reset-movement-after-reach-limit", org.dreeam.leaf.config.modules.opt.world.MaxProjectileChunkLoads.class, "perProjectileResetMovementAfterReachLimit");
+ addWorldMapping("small-optimizations.max-projectile-chunk-loads.per-projectile.remove-from-world-after-reach-limit", org.dreeam.leaf.config.modules.opt.world.MaxProjectileChunkLoads.class, "perProjectileRemoveFromWorldAfterReachLimit");
+ addWorldMapping("small-optimizations.reduced-intervals.check-stuck-in-wall", org.dreeam.leaf.config.modules.opt.world.ReducedIntervals.class, "checkStuckInWall");
+ addWorldMapping("small-optimizations.reduced-intervals.villager-item-repickup", org.dreeam.leaf.config.modules.opt.world.ReducedIntervals.class, "villagerItemRepickup");
+ addWorldMapping("small-optimizations.load-chunks.to-spawn-phantoms", org.dreeam.leaf.config.modules.opt.world.LoadChunks.class, "toSpawnPhantoms");
+ addWorldMapping("small-optimizations.load-chunks.to-activate-climbing-entities", org.dreeam.leaf.config.modules.opt.world.LoadChunks.class, "toActivateClimbingEntities");
+ addWorldMapping("gameplay-mechanics.fixes.broadcast-crit-animations-as-the-entity-being-critted", org.dreeam.leaf.config.modules.fixes.world.Fixes.class, "broadcastCritAnimationsAsTheEntityBeingCritted");
+ addWorldMapping("gameplay-mechanics.fixes.mc-238526", org.dreeam.leaf.config.modules.fixes.world.Fixes.class, "mc238526");
+ addWorldMapping("gameplay-mechanics.fixes.mc-121706", org.dreeam.leaf.config.modules.fixes.world.Fixes.class, "mc121706");
+ addWorldMapping("gameplay-mechanics.entities-can-random-stroll-into-non-ticking-chunks", org.dreeam.leaf.config.modules.gameplay.world.RandomStrollIntoNonTickingChunks.class, "enabled");
+ addWorldMapping("gameplay-mechanics.entity-wake-up-duration-ratio-standard-deviation", org.dreeam.leaf.config.modules.opt.world.EntityWakeUpDuration.class, "ratioStandardDeviation");
+ addWorldMapping("gameplay-mechanics.hide-flames-on-entities-with-fire-resistance", org.dreeam.leaf.config.modules.gameplay.world.HideFlamesOnEntitiesWithFireResistance.class, "enabled");
+ addWorldMapping("gameplay-mechanics.try-respawn-ender-dragon-after-end-crystal-place", org.dreeam.leaf.config.modules.gameplay.world.EnderDragonRespawn.class, "tryAfterEndCrystalPlace");
}
private static void addGlobalMapping(String oldPath, Class extends ConfigModule> moduleClass, String fieldName) {
@@ -77,11 +115,17 @@ public static void finalizeMigration(MinecraftServer server) {
return;
}
- boolean galeConfigFound = archive(configDirectory.resolve(GLOBAL_FILE));
- galeConfigFound |= archive(configDirectory.resolve(WORLD_DEFAULTS_FILE));
+ boolean galeConfigFound = archive(configDirectory.resolve(GLOBAL_FILE), Path.of(GLOBAL_FILE));
+ galeConfigFound |= archive(configDirectory.resolve(WORLD_DEFAULTS_FILE), Path.of(WORLD_DEFAULTS_FILE));
for (ServerLevel level : server.getAllLevels()) {
Path worldDirectory = server.storageSource.getDimensionPath(level.dimension());
- galeConfigFound |= archiveWorldOverride(worldDirectory.resolve(WORLD_OVERRIDE_FILE), worldDirectory);
+ Path absoluteWorldDirectory = worldDirectory.toAbsolutePath().normalize();
+ Path workingDirectory = Path.of("").toAbsolutePath().normalize();
+ Path worldContext = absoluteWorldDirectory.startsWith(workingDirectory)
+ ? workingDirectory.relativize(absoluteWorldDirectory)
+ : absoluteWorldDirectory.subpath(0, absoluteWorldDirectory.getNameCount());
+ Path backupPath = Path.of("world-overrides").resolve(worldContext).resolve(WORLD_OVERRIDE_FILE);
+ galeConfigFound |= archive(worldDirectory.resolve(WORLD_OVERRIDE_FILE), backupPath);
}
if (galeConfigFound) {
@@ -119,13 +163,23 @@ public static void finalizeMigration(MinecraftServer server) {
}
ConfigFile galeConfig = loadSrcConfig(galePath);
- if (galeConfig == null || !hasConfigValues(galeConfig, "")) {
+ if (galeConfig == null) {
+ return null;
+ }
+ boolean hasConfigValues = false;
+ for (String path : galeConfig.getKeys(false, true)) {
+ if (!IGNORED_PATHS.contains(path)) {
+ hasConfigValues = true;
+ break;
+ }
+ }
+ if (!hasConfigValues) {
return null;
}
Files.createFile(leafPath);
leafFileCreated = true;
- ConfigFile leafConfig = ConfigFile.loadConfig(leafFile);
+ ConfigFile leafConfig = ConfigFileIO.load(leafFile);
applyMappings(galeConfig, resolvedWorldMappings, leafConfig);
LeafWorldConfig migrated = LeafConfig.loadWorldOverride(leafConfig, defaults);
migrated.saveConfig();
@@ -186,78 +240,37 @@ private static Map resolveMappings(List mappings) {
return resolvedMappings;
}
- private static Path worldContext(Path worldDirectory) {
- Path absolute = worldDirectory.toAbsolutePath().normalize();
- Path workingDirectory = Path.of("").toAbsolutePath().normalize();
- return absolute.startsWith(workingDirectory)
- ? workingDirectory.relativize(absolute)
- : absolute.subpath(0, absolute.getNameCount());
- }
-
private static @Nullable ConfigFile loadSrcConfig(Path srcPath) {
if (!Files.isRegularFile(srcPath)) {
return null;
}
try {
- return ConfigFile.loadConfig(srcPath.toFile());
+ return ConfigFileIO.load(srcPath.toFile());
} catch (Exception exception) {
LOGGER.error("Failed to read Gale config {}; migration was skipped.", srcPath, exception);
return null;
}
}
- private static boolean archive(Path srcPath) {
- if (!Files.isRegularFile(srcPath)) return false;
- try {
- Path backupPath = Path.of(srcPath.getFileName().toString());
- moveToBackup(srcPath, backupPath);
- } catch (IOException exception) {
- LOGGER.error("Failed to back up Gale config {}; leaving it in place.", srcPath, exception);
- }
- return true;
- }
-
- private static boolean archiveWorldOverride(Path srcPath, Path worldDirectory) {
+ private static boolean archive(Path srcPath, Path backupPath) {
if (!Files.isRegularFile(srcPath)) return false;
try {
- Path fileName = Path.of(srcPath.getFileName().toString());
- Path backupPath = Path.of("world-overrides").resolve(worldContext(worldDirectory)).resolve(fileName);
- moveToBackup(srcPath, backupPath);
+ Path relative = backupPath.normalize();
+ if (relative.isAbsolute() || relative.startsWith("..")) {
+ throw new IOException("Invalid Gale backup path: " + relative);
+ }
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyMMddhhmmss");
+ Path backupDirectory = configDirectory.resolve("backup" + dateFormat.format(new Date()));
+ Path target = backupDirectory.resolve(relative).normalize();
+ Files.createDirectories(target.getParent());
+ Files.move(srcPath, target);
+ LOGGER.warn("Moved Gale config {} to {}.", srcPath, target);
} catch (IOException exception) {
LOGGER.error("Failed to back up Gale config {}; leaving it in place.", srcPath, exception);
}
return true;
}
- private static void moveToBackup(Path srcPath, Path backupPath) throws IOException {
- Path relative = backupPath.normalize();
- if (relative.isAbsolute() || relative.startsWith("..")) {
- throw new IOException("Invalid Gale backup path: " + relative);
- }
- SimpleDateFormat dateFormat = new SimpleDateFormat("yyMMddhhmmss");
- Path backupDirectory = configDirectory.resolve("backup" + dateFormat.format(new Date()));
- Path target = backupDirectory.resolve(relative).normalize();
- Files.createDirectories(target.getParent());
- Files.move(srcPath, target);
- LOGGER.warn("Moved Gale config {} to {}.", srcPath, target);
- }
-
- private static boolean hasConfigValues(Map, ?> values, String parent) {
- for (Map.Entry, ?> entry : values.entrySet()) {
- String name = String.valueOf(entry.getKey());
- String path = parent.isEmpty() ? name : parent + '.' + name;
- Object value = entry.getValue();
- if (value instanceof Map, ?> nested) {
- if (hasConfigValues(nested, path)) {
- return true;
- }
- } else if (!IGNORED_PATHS.contains(path)) {
- return true;
- }
- }
- return false;
- }
-
private record Mapping(String oldPath, Class> moduleClass, String fieldName) {
}
}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/fixes/world/Fixes.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/fixes/world/Fixes.java
new file mode 100644
index 0000000000..dd2a39358b
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/fixes/world/Fixes.java
@@ -0,0 +1,19 @@
+package org.dreeam.leaf.config.modules.fixes.world;
+
+import org.dreeam.leaf.config.ConfigCategory;
+import org.dreeam.leaf.config.WorldConfigModule;
+import org.dreeam.leaf.config.annotations.ConfigClassInfo;
+import org.dreeam.leaf.config.annotations.ConfigInfo;
+
+@ConfigClassInfo(category = ConfigCategory.FIXES, name = "gameplay-fixes")
+public final class Fixes implements WorldConfigModule {
+
+ @ConfigInfo(name = "broadcast-crit-animations-as-the-entity-being-critted")
+ public boolean broadcastCritAnimationsAsTheEntityBeingCritted = false;
+
+ @ConfigInfo(name = "mc-238526")
+ public boolean mc238526 = false;
+
+ @ConfigInfo(name = "mc-121706")
+ public boolean mc121706 = false;
+}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/global/GameplayMechanics.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/global/GameplayMechanics.java
new file mode 100644
index 0000000000..fffacd7240
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/global/GameplayMechanics.java
@@ -0,0 +1,13 @@
+package org.dreeam.leaf.config.modules.gameplay.global;
+
+import org.dreeam.leaf.config.ConfigCategory;
+import org.dreeam.leaf.config.ConfigModule;
+import org.dreeam.leaf.config.annotations.ConfigClassInfo;
+import org.dreeam.leaf.config.annotations.ConfigInfo;
+
+@ConfigClassInfo(category = ConfigCategory.GAMEPLAY, name = "book-writing")
+public final class GameplayMechanics implements ConfigModule {
+
+ @ConfigInfo(name = "enabled")
+ public static boolean enableBookWriting = true;
+}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/world/EnderDragonRespawn.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/world/EnderDragonRespawn.java
new file mode 100644
index 0000000000..aa9b3360a7
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/world/EnderDragonRespawn.java
@@ -0,0 +1,13 @@
+package org.dreeam.leaf.config.modules.gameplay.world;
+
+import org.dreeam.leaf.config.ConfigCategory;
+import org.dreeam.leaf.config.WorldConfigModule;
+import org.dreeam.leaf.config.annotations.ConfigClassInfo;
+import org.dreeam.leaf.config.annotations.ConfigInfo;
+
+@ConfigClassInfo(category = ConfigCategory.GAMEPLAY, name = "ender-dragon-respawn")
+public final class EnderDragonRespawn implements WorldConfigModule {
+
+ @ConfigInfo(name = "try-after-end-crystal-place")
+ public boolean tryAfterEndCrystalPlace = true;
+}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/world/HideFlamesOnEntitiesWithFireResistance.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/world/HideFlamesOnEntitiesWithFireResistance.java
new file mode 100644
index 0000000000..34f6dcd8ac
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/world/HideFlamesOnEntitiesWithFireResistance.java
@@ -0,0 +1,13 @@
+package org.dreeam.leaf.config.modules.gameplay.world;
+
+import org.dreeam.leaf.config.ConfigCategory;
+import org.dreeam.leaf.config.WorldConfigModule;
+import org.dreeam.leaf.config.annotations.ConfigClassInfo;
+import org.dreeam.leaf.config.annotations.ConfigInfo;
+
+@ConfigClassInfo(category = ConfigCategory.GAMEPLAY, name = "hide-flames-on-entities-with-fire-resistance")
+public final class HideFlamesOnEntitiesWithFireResistance implements WorldConfigModule {
+
+ @ConfigInfo(name = "enabled")
+ public boolean enabled = false;
+}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/world/RandomStrollIntoNonTickingChunks.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/world/RandomStrollIntoNonTickingChunks.java
new file mode 100644
index 0000000000..b178bc8e0b
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/world/RandomStrollIntoNonTickingChunks.java
@@ -0,0 +1,13 @@
+package org.dreeam.leaf.config.modules.gameplay.world;
+
+import org.dreeam.leaf.config.ConfigCategory;
+import org.dreeam.leaf.config.WorldConfigModule;
+import org.dreeam.leaf.config.annotations.ConfigClassInfo;
+import org.dreeam.leaf.config.annotations.ConfigInfo;
+
+@ConfigClassInfo(category = ConfigCategory.GAMEPLAY, name = "random-stroll-into-non-ticking-chunks")
+public final class RandomStrollIntoNonTickingChunks implements WorldConfigModule {
+
+ @ConfigInfo(name = "enabled")
+ public boolean enabled = true;
+}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/GlobalConfigExample.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/GlobalConfigExample.java
new file mode 100644
index 0000000000..521e74724a
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/GlobalConfigExample.java
@@ -0,0 +1,36 @@
+package org.dreeam.leaf.config.modules.misc;
+
+import org.dreeam.leaf.config.ConfigCategory;
+import org.dreeam.leaf.config.ConfigModule;
+import org.dreeam.leaf.config.annotations.ConfigClassInfo;
+import org.dreeam.leaf.config.annotations.ConfigInfo;
+import org.dreeam.leaf.config.annotations.DoNotLoad;
+import org.dreeam.leaf.config.annotations.HotReloadUnsupported;
+
+@ConfigClassInfo(category = ConfigCategory.MISC, name = "global-config-example", comments = {
+ "An example global configuration section.",
+ "一个全局配置节示例。"
+})
+public final class GlobalConfigExample implements ConfigModule {
+
+ @ConfigInfo(name = "reloadable-value", comments = {
+ "An example global value that supports hot reload.",
+ "一个支持热重载的全局配置示例。"
+ })
+ public static String reloadableValue = "global-default";
+
+ @HotReloadUnsupported
+ @ConfigInfo(name = "restart-required-value", comments = {
+ "An example global value that requires a restart.",
+ "一个需要重启才能生效的全局配置示例。"
+ })
+ public static String restartRequiredValue = "global-restart-required";
+
+ @DoNotLoad
+ public static String runtimeValue;
+
+ @Override
+ public void onLoaded() {
+ runtimeValue = reloadableValue + ':' + restartRequiredValue;
+ }
+}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/SecureSeed.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/SecureSeed.java
index b4d1e51396..6eb1dacaca 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/SecureSeed.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/SecureSeed.java
@@ -17,7 +17,7 @@ public SecureSeed() {
@Override
public void loadWorldConfig(LeafWorldConfig config) {
String path = "misc.secure-seed";
- if (config.isWorldDefaultsFile()) {
+ if (config.isWorldDefaults()) {
config.addCommentRegionBased(path, """
Once you enable secure seed, all ores and structures are generated with a 1024-bit seed
instead of vanilla's 64-bit seed, making seed cracking impossible.""", """
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/WorldConfigExample.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/WorldConfigExample.java
new file mode 100644
index 0000000000..0102ed8eea
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/WorldConfigExample.java
@@ -0,0 +1,21 @@
+package org.dreeam.leaf.config.modules.misc;
+
+import org.dreeam.leaf.config.ConfigCategory;
+import org.dreeam.leaf.config.WorldConfigModule;
+import org.dreeam.leaf.config.annotations.ConfigClassInfo;
+import org.dreeam.leaf.config.annotations.ConfigInfo;
+import org.dreeam.leaf.config.annotations.HotReloadUnsupported;
+
+@HotReloadUnsupported
+@ConfigClassInfo(category = ConfigCategory.MISC, name = "world-config-example", comments = {
+ "An example world configuration section.",
+ "一个世界配置节示例。"
+})
+public final class WorldConfigExample implements WorldConfigModule {
+
+ @ConfigInfo(name = "restart-required-value", comments = {
+ "An example world value that requires a restart.",
+ "一个需要重启才能生效的世界配置示例。"
+ })
+ public String restartRequiredValue = "world-restart-required";
+}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/global/Chat.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/global/Chat.java
new file mode 100644
index 0000000000..2111907bb6
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/global/Chat.java
@@ -0,0 +1,19 @@
+package org.dreeam.leaf.config.modules.misc.global;
+
+import org.dreeam.leaf.config.ConfigCategory;
+import org.dreeam.leaf.config.ConfigModule;
+import org.dreeam.leaf.config.annotations.ConfigClassInfo;
+import org.dreeam.leaf.config.annotations.ConfigInfo;
+
+@ConfigClassInfo(category = ConfigCategory.MISC, name = "chat-logging")
+public final class Chat implements ConfigModule {
+
+ @ConfigInfo(name = "empty-message-warning")
+ public static boolean emptyMessageWarning = false;
+
+ @ConfigInfo(name = "expired-message-warning")
+ public static boolean expiredMessageWarning = false;
+
+ @ConfigInfo(name = "not-secure-marker")
+ public static boolean notSecureMarker = true;
+}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/global/LastTickTimeInTpsCommand.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/global/LastTickTimeInTpsCommand.java
new file mode 100644
index 0000000000..a2ce6403d8
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/global/LastTickTimeInTpsCommand.java
@@ -0,0 +1,16 @@
+package org.dreeam.leaf.config.modules.misc.global;
+
+import org.dreeam.leaf.config.ConfigCategory;
+import org.dreeam.leaf.config.ConfigModule;
+import org.dreeam.leaf.config.annotations.ConfigClassInfo;
+import org.dreeam.leaf.config.annotations.ConfigInfo;
+
+@ConfigClassInfo(category = ConfigCategory.MISC, name = "last-tick-time-in-tps-command")
+public final class LastTickTimeInTpsCommand implements ConfigModule {
+
+ @ConfigInfo(name = "enabled")
+ public static boolean enabled = false;
+
+ @ConfigInfo(name = "add-oversleep")
+ public static boolean addOversleep = false;
+}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/global/LogToConsole.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/global/LogToConsole.java
new file mode 100644
index 0000000000..ff8d6d9bba
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/global/LogToConsole.java
@@ -0,0 +1,35 @@
+package org.dreeam.leaf.config.modules.misc.global;
+
+import org.dreeam.leaf.config.ConfigCategory;
+import org.dreeam.leaf.config.ConfigModule;
+import org.dreeam.leaf.config.annotations.ConfigClassInfo;
+import org.dreeam.leaf.config.annotations.ConfigInfo;
+
+@ConfigClassInfo(category = ConfigCategory.MISC, name = "log-to-console")
+public final class LogToConsole implements ConfigModule {
+
+ @ConfigInfo(name = "invalid-statistics")
+ public static boolean invalidStatistics = true;
+
+ @ConfigInfo(name = "ignored-advancements")
+ public static boolean ignoredAdvancements = true;
+
+ @ConfigInfo(name = "set-block-in-far-chunk")
+ public static boolean setBlockInFarChunk = true;
+
+ @ConfigInfo(name = "unrecognized-recipes")
+ public static boolean unrecognizedRecipes = false;
+
+ @ConfigInfo(name = "legacy-material-initialization")
+ public static boolean legacyMaterialInitialization = false;
+
+ @ConfigInfo(name = "null-id-disconnections")
+ public static boolean nullIdDisconnections = true;
+
+ @ConfigInfo(name = "player-login-locations")
+ public static boolean playerLoginLocations = true;
+
+ @ConfigInfo(name = "invalid-legacy-text-component")
+ public static boolean invalidLegacyTextComponent = true;
+
+}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/global/PluginLibraryLoader.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/global/PluginLibraryLoader.java
new file mode 100644
index 0000000000..a0157763ee
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/global/PluginLibraryLoader.java
@@ -0,0 +1,27 @@
+package org.dreeam.leaf.config.modules.misc.global;
+
+import org.bukkit.plugin.java.JavaPluginLoader;
+import org.dreeam.leaf.config.ConfigCategory;
+import org.dreeam.leaf.config.ConfigModule;
+import org.dreeam.leaf.config.annotations.ConfigClassInfo;
+import org.dreeam.leaf.config.annotations.ConfigInfo;
+
+@ConfigClassInfo(category = ConfigCategory.MISC, name = "plugin-library-loader")
+public final class PluginLibraryLoader implements ConfigModule {
+
+ @ConfigInfo(name = "downloads")
+ public static boolean downloads = true;
+
+ @ConfigInfo(name = "start-load-libraries-for-plugin")
+ public static boolean startLoadLibrariesForPlugin = true;
+
+ @ConfigInfo(name = "library-loaded")
+ public static boolean libraryLoaded = true;
+
+ @Override
+ public void onLoaded() {
+ JavaPluginLoader.logDownloads = downloads;
+ JavaPluginLoader.logStartLoadLibrariesForPlugin = startLoadLibrariesForPlugin;
+ JavaPluginLoader.logLibraryLoaded = libraryLoaded;
+ }
+}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/global/ChatOrderVerification.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/global/ChatOrderVerification.java
new file mode 100644
index 0000000000..87abaaeb80
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/global/ChatOrderVerification.java
@@ -0,0 +1,13 @@
+package org.dreeam.leaf.config.modules.network.global;
+
+import org.dreeam.leaf.config.ConfigCategory;
+import org.dreeam.leaf.config.ConfigModule;
+import org.dreeam.leaf.config.annotations.ConfigClassInfo;
+import org.dreeam.leaf.config.annotations.ConfigInfo;
+
+@ConfigClassInfo(category = ConfigCategory.NETWORK, name = "chat-order-verification")
+public final class ChatOrderVerification implements ConfigModule {
+
+ @ConfigInfo(name = "enabled")
+ public static boolean enabled = true;
+}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/global/Keepalive.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/global/Keepalive.java
new file mode 100644
index 0000000000..a067a199b2
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/global/Keepalive.java
@@ -0,0 +1,13 @@
+package org.dreeam.leaf.config.modules.network.global;
+
+import org.dreeam.leaf.config.ConfigCategory;
+import org.dreeam.leaf.config.ConfigModule;
+import org.dreeam.leaf.config.annotations.ConfigClassInfo;
+import org.dreeam.leaf.config.annotations.ConfigInfo;
+
+@ConfigClassInfo(category = ConfigCategory.NETWORK, name = "keepalive")
+public final class Keepalive implements ConfigModule {
+
+ @ConfigInfo(name = "send-multiple")
+ public static boolean sendMultiple = false;
+}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/global/PremiumAccountSlowLoginTimeout.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/global/PremiumAccountSlowLoginTimeout.java
new file mode 100644
index 0000000000..4a4748b6b5
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/global/PremiumAccountSlowLoginTimeout.java
@@ -0,0 +1,13 @@
+package org.dreeam.leaf.config.modules.network.global;
+
+import org.dreeam.leaf.config.ConfigCategory;
+import org.dreeam.leaf.config.ConfigModule;
+import org.dreeam.leaf.config.annotations.ConfigClassInfo;
+import org.dreeam.leaf.config.annotations.ConfigInfo;
+
+@ConfigClassInfo(category = ConfigCategory.NETWORK, name = "premium-account-slow-login-timeout")
+public final class PremiumAccountSlowLoginTimeout implements ConfigModule {
+
+ @ConfigInfo(name = "ticks")
+ public static int ticks = -1;
+}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/global/ReducedIntervals.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/global/ReducedIntervals.java
new file mode 100644
index 0000000000..52a59b7281
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/global/ReducedIntervals.java
@@ -0,0 +1,22 @@
+package org.dreeam.leaf.config.modules.opt.global;
+
+import net.minecraft.server.level.ServerPlayer;
+import org.dreeam.leaf.config.ConfigCategory;
+import org.dreeam.leaf.config.ConfigModule;
+import org.dreeam.leaf.config.annotations.ConfigClassInfo;
+import org.dreeam.leaf.config.annotations.ConfigInfo;
+
+@ConfigClassInfo(category = ConfigCategory.PERF, name = "reduced-intervals")
+public final class ReducedIntervals implements ConfigModule {
+
+ @ConfigInfo(name = "increase-time-statistics")
+ public static int increaseTimeStatistics = 1;
+
+ @ConfigInfo(name = "update-entity-line-of-sight")
+ public static int updateEntityLineOfSight = 4;
+
+ @Override
+ public void onLoaded() {
+ ServerPlayer.increaseTimeStatisticsInterval = Math.max(1, increaseTimeStatistics);
+ }
+}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/world/EntityWakeUpDuration.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/world/EntityWakeUpDuration.java
new file mode 100644
index 0000000000..ae3bc2a3f1
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/world/EntityWakeUpDuration.java
@@ -0,0 +1,13 @@
+package org.dreeam.leaf.config.modules.opt.world;
+
+import org.dreeam.leaf.config.ConfigCategory;
+import org.dreeam.leaf.config.WorldConfigModule;
+import org.dreeam.leaf.config.annotations.ConfigClassInfo;
+import org.dreeam.leaf.config.annotations.ConfigInfo;
+
+@ConfigClassInfo(category = ConfigCategory.PERF, name = "entity-wake-up-duration")
+public final class EntityWakeUpDuration implements WorldConfigModule {
+
+ @ConfigInfo(name = "ratio-standard-deviation")
+ public double ratioStandardDeviation = 0.2;
+}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/world/LoadChunks.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/world/LoadChunks.java
new file mode 100644
index 0000000000..bb1ce7c312
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/world/LoadChunks.java
@@ -0,0 +1,16 @@
+package org.dreeam.leaf.config.modules.opt.world;
+
+import org.dreeam.leaf.config.ConfigCategory;
+import org.dreeam.leaf.config.WorldConfigModule;
+import org.dreeam.leaf.config.annotations.ConfigClassInfo;
+import org.dreeam.leaf.config.annotations.ConfigInfo;
+
+@ConfigClassInfo(category = ConfigCategory.PERF, name = "load-chunks")
+public final class LoadChunks implements WorldConfigModule {
+
+ @ConfigInfo(name = "to-spawn-phantoms")
+ public boolean toSpawnPhantoms = false;
+
+ @ConfigInfo(name = "to-activate-climbing-entities")
+ public boolean toActivateClimbingEntities = false;
+}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/world/MaxProjectileChunkLoads.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/world/MaxProjectileChunkLoads.java
new file mode 100644
index 0000000000..d8bbd5a046
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/world/MaxProjectileChunkLoads.java
@@ -0,0 +1,22 @@
+package org.dreeam.leaf.config.modules.opt.world;
+
+import org.dreeam.leaf.config.ConfigCategory;
+import org.dreeam.leaf.config.WorldConfigModule;
+import org.dreeam.leaf.config.annotations.ConfigClassInfo;
+import org.dreeam.leaf.config.annotations.ConfigInfo;
+
+@ConfigClassInfo(category = ConfigCategory.PERF, name = "max-projectile-chunk-loads")
+public final class MaxProjectileChunkLoads implements WorldConfigModule {
+
+ @ConfigInfo(name = "per-tick")
+ public int perTick = 10;
+
+ @ConfigInfo(name = "per-projectile.max")
+ public int perProjectileMax = 10;
+
+ @ConfigInfo(name = "per-projectile.reset-movement-after-reach-limit")
+ public boolean perProjectileResetMovementAfterReachLimit = false;
+
+ @ConfigInfo(name = "per-projectile.remove-from-world-after-reach-limit")
+ public boolean perProjectileRemoveFromWorldAfterReachLimit = false;
+}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/world/OptimizedSheepOffspringColor.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/world/OptimizedSheepOffspringColor.java
new file mode 100644
index 0000000000..94f80f439c
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/world/OptimizedSheepOffspringColor.java
@@ -0,0 +1,13 @@
+package org.dreeam.leaf.config.modules.opt.world;
+
+import org.dreeam.leaf.config.ConfigCategory;
+import org.dreeam.leaf.config.WorldConfigModule;
+import org.dreeam.leaf.config.annotations.ConfigClassInfo;
+import org.dreeam.leaf.config.annotations.ConfigInfo;
+
+@ConfigClassInfo(category = ConfigCategory.PERF, name = "optimized-sheep-offspring-color")
+public final class OptimizedSheepOffspringColor implements WorldConfigModule {
+
+ @ConfigInfo(name = "enabled")
+ public boolean enabled = true;
+}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/world/ReducedIntervals.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/world/ReducedIntervals.java
new file mode 100644
index 0000000000..981638eb07
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/world/ReducedIntervals.java
@@ -0,0 +1,16 @@
+package org.dreeam.leaf.config.modules.opt.world;
+
+import org.dreeam.leaf.config.ConfigCategory;
+import org.dreeam.leaf.config.WorldConfigModule;
+import org.dreeam.leaf.config.annotations.ConfigClassInfo;
+import org.dreeam.leaf.config.annotations.ConfigInfo;
+
+@ConfigClassInfo(category = ConfigCategory.PERF, name = "reduced-intervals")
+public final class ReducedIntervals implements WorldConfigModule {
+
+ @ConfigInfo(name = "check-stuck-in-wall")
+ public int checkStuckInWall = 10;
+
+ @ConfigInfo(name = "villager-item-repickup")
+ public int villagerItemRepickup = 100;
+}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/world/SaveFireworks.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/world/SaveFireworks.java
new file mode 100644
index 0000000000..a9b17a9fbd
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/world/SaveFireworks.java
@@ -0,0 +1,13 @@
+package org.dreeam.leaf.config.modules.opt.world;
+
+import org.dreeam.leaf.config.ConfigCategory;
+import org.dreeam.leaf.config.WorldConfigModule;
+import org.dreeam.leaf.config.annotations.ConfigClassInfo;
+import org.dreeam.leaf.config.annotations.ConfigInfo;
+
+@ConfigClassInfo(category = ConfigCategory.PERF, name = "save-fireworks")
+public final class SaveFireworks implements WorldConfigModule {
+
+ @ConfigInfo(name = "enabled")
+ public boolean enabled = true;
+}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/util/ConfigFileIO.java b/leaf-server/src/main/java/org/dreeam/leaf/config/util/ConfigFileIO.java
new file mode 100644
index 0000000000..512ed192e2
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/config/util/ConfigFileIO.java
@@ -0,0 +1,151 @@
+package org.dreeam.leaf.config.util;
+
+import io.github.thatsmusic99.configurationmaster.api.ConfigFile;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.jspecify.annotations.Nullable;
+import org.yaml.snakeyaml.LoaderOptions;
+import org.yaml.snakeyaml.Yaml;
+import org.yaml.snakeyaml.constructor.SafeConstructor;
+import org.yaml.snakeyaml.error.YAMLException;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * ConfigurationMaster file loading and atomic saving utilities.
+ */
+public final class ConfigFileIO {
+
+ private static final Logger LOGGER = LogManager.getLogger(ConfigFileIO.class.getSimpleName());
+ private static final int MAX_CODE_POINTS = 100 * 1024 * 1024;
+
+ public static ConfigFile load(File file) throws Exception {
+ Path path = file.toPath();
+ if (!Files.isRegularFile(path)) {
+ return ConfigFile.loadConfig(file);
+ }
+
+ LoaderOptions loaderOptions = new LoaderOptions();
+ loaderOptions.setCodePointLimit(MAX_CODE_POINTS); // Increase YAML file size limit
+ Yaml yaml = new Yaml(new SafeConstructor(loaderOptions));
+ try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
+ yaml.load(reader);
+ } catch (YAMLException exception) {
+ throw new IOException("Malformed YAML configuration: " + path, exception);
+ }
+ return ConfigFile.loadConfig(file);
+ }
+
+ public static void saveAtomically(ConfigFile... configs) throws Exception {
+ Set