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 worldModuleClass = (Class) 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 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 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 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) 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 worldModuleClass = (Class) 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 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 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 worldModuleClass = + (Class) 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 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 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 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 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) 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 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 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) 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 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) 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 worldModuleClass = - (Class) 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 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 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 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 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 targets = new HashSet<>(); + List serializedConfigs = new ArrayList<>(configs.length); + for (ConfigFile config : configs) { + Objects.requireNonNull(config, "config"); + Path target = config.getFile().toPath().toAbsolutePath().normalize(); + if (!targets.add(target)) { + throw new IllegalArgumentException("Cannot save the same config file twice: " + target); + } + serializedConfigs.add(new SerializedConfig(target, config.saveToString())); + } + + List stagedConfigs = new ArrayList<>(serializedConfigs.size()); + List temporaryFiles = new ArrayList<>(serializedConfigs.size() * 2); + try { + for (SerializedConfig serializedConfig : serializedConfigs) { + Path target = serializedConfig.target(); + Path parent = Objects.requireNonNull(target.getParent(), "Config file has no parent: " + target); + Files.createDirectories(parent); + + String prefix = "." + target.getFileName() + "."; + Path staged = Files.createTempFile(parent, prefix, ".tmp"); + temporaryFiles.add(staged); + + boolean targetExisted = Files.exists(target); + @Nullable Path rollback = null; + if (targetExisted) { + rollback = Files.createTempFile(parent, prefix, ".rollback"); + temporaryFiles.add(rollback); + Files.copy(target, rollback, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES); + try { + Files.setPosixFilePermissions(staged, Files.getPosixFilePermissions(target)); + } catch (UnsupportedOperationException ignored) { + } + } + + Files.writeString( + staged, + serializedConfig.content(), + StandardCharsets.UTF_8, + StandardOpenOption.TRUNCATE_EXISTING, + StandardOpenOption.WRITE + ); + stagedConfigs.add(new StagedConfig(target, staged, rollback, targetExisted)); + } + + int committed = 0; + try { + for (StagedConfig stagedConfig : stagedConfigs) { + Files.move( + stagedConfig.staged(), + stagedConfig.target(), + StandardCopyOption.ATOMIC_MOVE, + StandardCopyOption.REPLACE_EXISTING + ); + committed++; + } + } catch (Exception exception) { + for (int index = committed - 1; index >= 0; index--) { + StagedConfig stagedConfig = stagedConfigs.get(index); + try { + if (stagedConfig.targetExisted()) { + Files.move( + Objects.requireNonNull(stagedConfig.rollback()), + stagedConfig.target(), + StandardCopyOption.ATOMIC_MOVE, + StandardCopyOption.REPLACE_EXISTING + ); + } else { + Files.deleteIfExists(stagedConfig.target()); + } + } catch (Exception rollbackException) { + exception.addSuppressed(rollbackException); + if (stagedConfig.rollback() != null) { + temporaryFiles.remove(stagedConfig.rollback()); + LOGGER.error( + "Failed to restore config {}; rollback copy was left at {}.", + stagedConfig.target(), stagedConfig.rollback(), rollbackException + ); + } + } + } + throw exception; + } + } finally { + for (Path temporaryFile : temporaryFiles) { + try { + Files.deleteIfExists(temporaryFile); + } catch (IOException exception) { + LOGGER.warn("Failed to delete temporary config file {}.", temporaryFile, exception); + } + } + } + } + + private record SerializedConfig(Path target, String content) { + } + + private record StagedConfig(Path target, Path staged, @Nullable Path rollback, boolean targetExisted) { + } +} From be87fcdac8182d8d39ec640a3c8fe59d0c6c758d Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Mon, 17 Aug 2026 13:52:19 +0800 Subject: [PATCH 13/14] Refactor async modules --- .../leaf/async/path/AsyncPathProcessor.java | 8 +- .../config/modules/async/AsyncChunkSend.java | 39 ++++------ .../modules/async/AsyncMobSpawning.java | 48 ++++-------- .../modules/async/AsyncPathfinding.java | 77 +++++++++---------- .../modules/async/AsyncPlayerDataSave.java | 25 +++--- .../modules/async/MultithreadedTracker.java | 51 +++++------- .../SparklyPaperParallelWorldTicking.java | 63 +++++++-------- 7 files changed, 135 insertions(+), 176 deletions(-) diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/path/AsyncPathProcessor.java b/leaf-server/src/main/java/org/dreeam/leaf/async/path/AsyncPathProcessor.java index 1caa996fef..3d6f87bbe4 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/path/AsyncPathProcessor.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/path/AsyncPathProcessor.java @@ -78,15 +78,15 @@ private static int getCorePoolSize() { } private static int getMaxPoolSize() { - return AsyncPathfinding.asyncPathfindingMaxThreads; + return AsyncPathfinding.maxThreads; } private static long getKeepAliveTime() { - return AsyncPathfinding.asyncPathfindingKeepalive; + return AsyncPathfinding.keepalive; } private static BlockingQueue getQueueImpl() { - final int queueCapacity = AsyncPathfinding.asyncPathfindingQueueSize; + final int queueCapacity = AsyncPathfinding.queueSize; return new LinkedBlockingQueue<>(queueCapacity); } @@ -103,7 +103,7 @@ private static RejectedExecutionHandler getRejectedPolicy() { return (Runnable rejectedTask, ThreadPoolExecutor executor) -> { BlockingQueue workQueue = executor.getQueue(); if (!executor.isShutdown()) { - switch (AsyncPathfinding.asyncPathfindingRejectPolicy) { + switch (AsyncPathfinding.rejectPolicyType) { case FLUSH_ALL -> { if (!workQueue.isEmpty()) { List pendingTasks = new ArrayList<>(workQueue.size()); diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncChunkSend.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncChunkSend.java index 7672052a90..3bbddf6098 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncChunkSend.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncChunkSend.java @@ -1,32 +1,19 @@ package org.dreeam.leaf.config.modules.async; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; -public class AsyncChunkSend extends ConfigModule { - - public String basePath() { - return ConfigCategory.ASYNC.basePath() + ".async-chunk-send"; - } +@HotReloadUnsupported +@ConfigClassInfo(category = ConfigCategory.ASYNC, name = "async-chunk-send", comments = { + """ + Makes chunk packet preparation and sending asynchronous to improve server performance. + This can significantly reduce main thread load when many players are loading chunks.""", + """ + 使区块数据包准备和发送异步化以提高服务器性能. + 当许多玩家同时加载区块时, 这可以显著减少主线程负载.""" +}) +public class AsyncChunkSend implements ConfigModule { + @ConfigInfo(name = "enabled") public static boolean enabled = false; - private static boolean asyncChunkSendInitialized; - - @Override - public void onLoaded() { - globalConfig.addCommentRegionBased(basePath(), """ - Makes chunk packet preparation and sending asynchronous to improve server performance. - This can significantly reduce main thread load when many players are loading chunks.""", - """ - 使区块数据包准备和发送异步化以提高服务器性能. - 当许多玩家同时加载区块时, 这可以显著减少主线程负载."""); - - if (asyncChunkSendInitialized) { - globalConfig.getConfigSection(basePath()); - return; - } - asyncChunkSendInitialized = true; - - enabled = globalConfig.getBoolean(basePath() + ".enabled", enabled); - } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncMobSpawning.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncMobSpawning.java index a352f06c86..dbb09ccb1b 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncMobSpawning.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncMobSpawning.java @@ -1,37 +1,23 @@ package org.dreeam.leaf.config.modules.async; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; -public class AsyncMobSpawning extends ConfigModule { - - public String basePath() { - return ConfigCategory.ASYNC.basePath() + ".async-mob-spawning"; - } +@HotReloadUnsupported +@ConfigClassInfo(category = ConfigCategory.ASYNC, name = "async-mob-spawning", comments = { + """ + Whether or not asynchronous mob spawning should be enabled. + On servers with many entities, this can improve performance by up to 15%. You must have + paper's per-player-mob-spawns setting set to true for this to work. + One quick note - this does not actually spawn mobs async (that would be very unsafe). + This just offloads some expensive calculations that are required for mob spawning.""", + """ + 是否异步化生物生成. + 在实体较多的服务器上, 异步生成可最高带来 15% 的性能提升. + 须在Paper配置文件中打开 per-player-mob-spawns 才能生效.""" +}) +public class AsyncMobSpawning implements ConfigModule { + @ConfigInfo(name = "enabled") public static boolean enabled = true; - private static boolean asyncMobSpawningInitialized; - - @Override - public void onLoaded() { - globalConfig.addCommentRegionBased(basePath(), """ - Whether or not asynchronous mob spawning should be enabled. - On servers with many entities, this can improve performance by up to 15%. You must have - paper's per-player-mob-spawns setting set to true for this to work. - One quick note - this does not actually spawn mobs async (that would be very unsafe). - This just offloads some expensive calculations that are required for mob spawning.""", - """ - 是否异步化生物生成. - 在实体较多的服务器上, 异步生成可最高带来 15% 的性能提升. - 须在Paper配置文件中打开 per-player-mob-spawns 才能生效."""); - - // This prevents us from changing the value during a reload. - if (asyncMobSpawningInitialized) { - globalConfig.getConfigSection(basePath()); - return; - } - asyncMobSpawningInitialized = true; - - enabled = globalConfig.getBoolean(basePath() + ".enabled", enabled); - } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPathfinding.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPathfinding.java index a258311e62..9afbbcdd82 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPathfinding.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPathfinding.java @@ -1,66 +1,63 @@ package org.dreeam.leaf.config.modules.async; import org.dreeam.leaf.async.path.PathfindTaskRejectPolicy; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; -import org.dreeam.leaf.config.LeafConfig; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; -public class AsyncPathfinding extends ConfigModule { - - public String basePath() { - return ConfigCategory.ASYNC.basePath() + ".async-pathfinding"; - } +@HotReloadUnsupported +@ConfigClassInfo(category = ConfigCategory.ASYNC, name = "async-pathfinding") +public class AsyncPathfinding implements ConfigModule { + @ConfigInfo(name = "enabled") public static boolean enabled = false; - public static int asyncPathfindingMaxThreads = 0; - public static int asyncPathfindingKeepalive = 60; - public static int asyncPathfindingQueueSize = 0; - public static PathfindTaskRejectPolicy asyncPathfindingRejectPolicy = PathfindTaskRejectPolicy.FLUSH_ALL; - private static boolean asyncPathfindingInitialized; + + @ConfigInfo(name = "max-threads") + public static int maxThreads = 0; + + @ConfigInfo(name = "keepalive") + public static int keepalive = 60; + + @ConfigInfo(name = "queue-size") + public static int queueSize = 0; + + @ConfigInfo(name = "reject-policy", comments = { + """ + The policy to use when the queue is full and a new task is submitted. + FLUSH_ALL: All pending tasks will be run on server thread. + CALLER_RUNS: Newly submitted task will be run on server thread.""", + """ + 当队列满时, 新提交的任务将使用以下策略处理. + FLUSH_ALL: 所有等待中的任务都将在主线程上运行. + CALLER_RUNS: 新提交的任务将在主线程上运行.""" + }) + public static String rejectPolicy = "CALLER_RUNS"; + + public static @DoNotLoad PathfindTaskRejectPolicy rejectPolicyType = PathfindTaskRejectPolicy.CALLER_RUNS; @Override public void onLoaded() { - globalConfig.addCommentRegionBased(basePath() + ".reject-policy", """ - The policy to use when the queue is full and a new task is submitted. - FLUSH_ALL: All pending tasks will be run on server thread. - CALLER_RUNS: Newly submitted task will be run on server thread.""", - """ - 当队列满时, 新提交的任务将使用以下策略处理. - FLUSH_ALL: 所有等待中的任务都将在主线程上运行. - CALLER_RUNS: 新提交的任务将在主线程上运行.""" - ); - if (asyncPathfindingInitialized) { - globalConfig.getConfigSection(basePath()); - return; - } - asyncPathfindingInitialized = true; - final int availableProcessors = Runtime.getRuntime().availableProcessors(); - enabled = globalConfig.getBoolean(basePath() + ".enabled", enabled); - asyncPathfindingMaxThreads = globalConfig.getInt(basePath() + ".max-threads", asyncPathfindingMaxThreads); - asyncPathfindingKeepalive = globalConfig.getInt(basePath() + ".keepalive", asyncPathfindingKeepalive); - asyncPathfindingQueueSize = globalConfig.getInt(basePath() + ".queue-size", asyncPathfindingQueueSize); - if (asyncPathfindingMaxThreads <= 0) { - asyncPathfindingMaxThreads = Math.max(availableProcessors / 4, 1); + if (maxThreads <= 0) { + maxThreads = Math.max(availableProcessors / 4, 1); } if (!enabled) { - asyncPathfindingMaxThreads = 0; + maxThreads = 0; } - if (asyncPathfindingQueueSize <= 0) { - asyncPathfindingQueueSize = asyncPathfindingMaxThreads * 256; + if (queueSize <= 0) { + queueSize = maxThreads * 256; } - asyncPathfindingRejectPolicy = PathfindTaskRejectPolicy.fromString(globalConfig.getString(basePath() + ".reject-policy", - availableProcessors >= 12 && asyncPathfindingQueueSize < 512 + rejectPolicyType = PathfindTaskRejectPolicy.fromString(globalConfig.getString(basePath() + ".reject-policy", + availableProcessors >= 12 && queueSize < 512 ? PathfindTaskRejectPolicy.FLUSH_ALL.toString() : PathfindTaskRejectPolicy.CALLER_RUNS.toString()) ); if (enabled) { - LeafConfig.LOGGER.info("Using {} threads for Async Pathfinding", asyncPathfindingMaxThreads); + LeafConfig.LOGGER.info("Using {} threads for Async Pathfinding", maxThreads); org.dreeam.leaf.async.path.AsyncPathProcessor.init(); } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPlayerDataSave.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPlayerDataSave.java index 141ec7385b..076d701d61 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPlayerDataSave.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPlayerDataSave.java @@ -1,25 +1,22 @@ package org.dreeam.leaf.config.modules.async; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; -public class AsyncPlayerDataSave extends ConfigModule { - - public String basePath() { - return ConfigCategory.ASYNC.basePath() + ".async-playerdata-save"; - } +@HotReloadUnsupported +@ConfigClassInfo(category = ConfigCategory.ASYNC, name = "async-playerdata-save", comments = { + """ + Make PlayerData saving asynchronously.""", + """ + 异步保存玩家数据.""" +}) +public class AsyncPlayerDataSave implements ConfigModule { + @ConfigInfo(name = "enabled") public static boolean enabled = false; @Override public void onLoaded() { - globalConfig.addCommentRegionBased(basePath(), """ - Make PlayerData saving asynchronously.""", - """ - 异步保存玩家数据."""); - - enabled = globalConfig.getBoolean(basePath() + ".enabled", enabled); - if (enabled) { org.dreeam.leaf.async.AsyncPlayerDataSaving.init(); } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/MultithreadedTracker.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/MultithreadedTracker.java index 03145a6c2e..ac6fa135f8 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/MultithreadedTracker.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/MultithreadedTracker.java @@ -1,41 +1,30 @@ package org.dreeam.leaf.config.modules.async; import org.dreeam.leaf.async.tracker.AsyncTracker; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; -import org.dreeam.leaf.config.LeafConfig; -import org.dreeam.leaf.config.annotations.Experimental; - -public class MultithreadedTracker extends ConfigModule { - - public String basePath() { - return ConfigCategory.ASYNC.basePath() + ".async-entity-tracker"; - } - - @Experimental - public static boolean enabled = false; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; + +@HotReloadUnsupported +@ConfigClassInfo(category = ConfigCategory.ASYNC, name = "async-entity-tracker", comments = { + """ + ** Experimental Feature ** + Make entity tracking asynchronously, can improve performance significantly, + especially in some massive entities in small area situations.""", + """ + ** 实验性功能 ** + 异步实体跟踪, + 在实体数量多且密集的情况下效果明显.""" +}) +public class MultithreadedTracker implements ConfigModule { + + @ConfigInfo(name = "enabled") + public static @Experimental boolean enabled = false; + + @ConfigInfo(name = "threads") public static int threads = 0; - private static boolean asyncMultithreadedTrackerInitialized; @Override public void onLoaded() { - globalConfig.addCommentRegionBased(basePath(), """ - ** Experimental Feature ** - Make entity tracking asynchronously, can improve performance significantly, - especially in some massive entities in small area situations.""", """ - ** 实验性功能 ** - 异步实体跟踪, - 在实体数量多且密集的情况下效果明显."""); - - if (asyncMultithreadedTrackerInitialized) { - globalConfig.getConfigSection(basePath()); - return; - } - asyncMultithreadedTrackerInitialized = true; - - enabled = globalConfig.getBoolean(basePath() + ".enabled", false); - threads = globalConfig.getInt(basePath() + ".threads", 0); - if (threads <= 0) { threads = Math.min(Runtime.getRuntime().availableProcessors(), 4); } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/SparklyPaperParallelWorldTicking.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/SparklyPaperParallelWorldTicking.java index 37949132f4..48e7d5bee3 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/SparklyPaperParallelWorldTicking.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/SparklyPaperParallelWorldTicking.java @@ -1,53 +1,56 @@ package org.dreeam.leaf.config.modules.async; import org.dreeam.leaf.async.world.UnsafeReadPolicy; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; -import org.dreeam.leaf.config.LeafConfig; -import org.dreeam.leaf.config.annotations.Experimental; - -public class SparklyPaperParallelWorldTicking extends ConfigModule { - - public String basePath() { - return ConfigCategory.ASYNC.basePath() + ".parallel-world-ticking"; - } - - @Experimental - public static boolean enabled = false; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; + +@ConfigClassInfo(category = ConfigCategory.ASYNC, name = "parallel-world-ticking", comments = { + """ + **Experimental feature** + Enables parallel world ticking to improve performance on multi-core systems.""", + """ + **实验性功能** + 启用并行世界处理以提高多核 CPU 使用率.""" +}) +public class SparklyPaperParallelWorldTicking implements ConfigModule { + + @ConfigInfo(name = "enabled") + public static @Experimental boolean enabled = false; + + @ConfigInfo(name = "threads") public static int threads = 8; + + @ConfigInfo(name = "log-container-creation-stacktraces") public static boolean logContainerCreationStacktraces = false; + + @ConfigInfo(name = "disable-hard-throw") public static boolean disableHardThrow = false; + + @ConfigInfo(name = "async-unsafe-read-handling") + public static String asyncUnsafeReadHandling = "DISABLED"; + + public static @DoNotLoad UnsafeReadPolicy asyncUnsafeReadHandlingPolicy = UnsafeReadPolicy.DISABLED; + @Deprecated - public static Boolean runAsyncTasksSync; - public static UnsafeReadPolicy asyncUnsafeReadHandling = UnsafeReadPolicy.DISABLED; + public static @DoNotLoad Boolean runAsyncTasksSync; @Override public void onLoaded() { - globalConfig.addCommentRegionBased(basePath(), """ - **Experimental feature** - Enables parallel world ticking to improve performance on multi-core systems.""", - """ - **实验性功能** - 启用并行世界处理以提高多核 CPU 使用率."""); - - enabled = globalConfig.getBoolean(basePath() + ".enabled", enabled); - threads = globalConfig.getInt(basePath() + ".threads", threads); if (enabled) { if (threads <= 0) threads = 8; } else { threads = 0; } - logContainerCreationStacktraces = globalConfig.getBoolean(basePath() + ".log-container-creation-stacktraces", logContainerCreationStacktraces); logContainerCreationStacktraces = enabled && logContainerCreationStacktraces; - disableHardThrow = globalConfig.getBoolean(basePath() + ".disable-hard-throw", disableHardThrow); disableHardThrow = enabled && disableHardThrow; - asyncUnsafeReadHandling = UnsafeReadPolicy.fromString(globalConfig.getString(basePath() + ".async-unsafe-read-handling", asyncUnsafeReadHandling.toString())); - // Transfer old config - runAsyncTasksSync = globalConfig.getBoolean(basePath() + ".run-async-tasks-sync"); + asyncUnsafeReadHandlingPolicy = UnsafeReadPolicy.fromString(asyncUnsafeReadHandling); + + // TODO: Transfer old config + runAsyncTasksSync = LeafConfig.globalConfig().getBoolean("async.parallel-world-ticking.run-async-tasks-sync"); if (runAsyncTasksSync != null && runAsyncTasksSync) { - LeafConfig.LOGGER.warn("The setting '{}.run-async-tasks-sync' is deprecated, removed automatically. Use 'async-unsafe-read-handling: BUFFERED' for buffered reads instead.", basePath()); + LeafConfig.LOGGER.warn("The setting 'async.parallel-world-ticking.run-async-tasks-sync' is deprecated, removed automatically. Use 'async-unsafe-read-handling: BUFFERED' for buffered reads instead."); } if (enabled) { From ffcbbea6b601afd519a412f807aa9ea7fd2f5d0c Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Thu, 20 Aug 2026 13:35:07 +0800 Subject: [PATCH 14/14] Some work --- .../features/0326-Leaf-config-changes.patch | 2 +- .../0019-Including-5s-in-getTPS.patch | 2 +- .../async/path/PathfindTaskRejectPolicy.java | 4 +- .../org/dreeam/leaf/config/LeafConfig.java | 26 ++-- .../dreeam/leaf/config/LeafWorldConfig.java | 4 +- .../config/annotations/ConfigClassInfo.java | 2 +- .../modules/async/AsyncPathfinding.java | 14 +- .../leaf/config/modules/fixes/MCBugFix.java | 81 ++++++----- .../PreventMoveIntoWeakLoadedChunks.java | 44 +++--- .../config/modules/fixes/world/Fixes.java | 6 +- .../config/modules/gameplay/AfkCommand.java | 29 ++-- .../config/modules/gameplay/BookWriting.java | 11 ++ .../ConfigurableInventoryOverflowEvent.java | 46 +++---- .../ConfigurableMaxUseItemDistance.java | 39 +++--- .../gameplay/ConfigurableTripWireDupe.java | 17 +-- .../gameplay/DeathItemDropKnockback.java | 46 +++---- .../modules/gameplay/IceAndSnowChance.java | 13 +- .../config/modules/gameplay/Knockback.java | 61 ++++----- .../modules/gameplay/OnlyPlayerPushable.java | 23 ++-- .../modules/gameplay/SpawnerSettings.java | 126 ++++++++---------- .../gameplay/UseSpigotItemMergingMech.java | 17 +-- .../modules/gameplay/VanillaHopper.java | 17 +-- .../gameplay/global/GameplayMechanics.java | 13 -- .../gameplay/world/EnderDragonRespawn.java | 6 +- ...ideFlamesOnEntitiesWithFireResistance.java | 6 +- .../RandomStrollIntoNonTickingChunks.java | 6 +- .../modules/misc/ConnectionMessage.java | 59 ++++---- .../modules/misc/DisableWorldDataSaving.java | 30 ++--- .../modules/misc/GlobalConfigExample.java | 36 ----- .../modules/misc/Including5sInGetTPS.java | 11 ++ .../modules/misc/Including5sIngetTPS.java | 18 --- .../config/modules/misc/LagCompensation.java | 37 +++-- .../modules/misc/RegionFormatConfig.java | 101 +++++++------- .../RemoveChangeNonEditableSignWarning.java | 24 ++-- .../modules/misc/RemoveSpigotCheckBungee.java | 27 ++-- .../leaf/config/modules/misc/SecureSeed.java | 39 ------ .../leaf/config/modules/misc/SentryDSN.java | 49 +++---- .../leaf/config/modules/misc/ServerBrand.java | 20 +-- .../modules/misc/UnknownCommandMessage.java | 37 +++-- .../modules/misc/VanillaUsernameCheck.java | 113 ++++++++-------- .../modules/misc/WorldConfigExample.java | 21 --- .../leaf/config/modules/misc/global/Chat.java | 6 +- .../misc/global/LastTickTimeInTpsCommand.java | 6 +- .../modules/misc/global/LogToConsole.java | 6 +- .../misc/global/PluginLibraryLoader.java | 6 +- .../config/modules/misc/world/SecureSeed.java | 21 +++ .../modules/network/AlternativeJoin.java | 22 ++- .../modules/network/ChatMessageSignature.java | 31 ++--- .../network/ChatOrderVerification.java | 11 ++ .../config/modules/network/Keepalive.java | 11 ++ .../OptimizeNonFlushPacketSending.java | 53 +++----- .../PremiumAccountSlowLoginTimeout.java | 11 ++ .../modules/network/ProtocolSupport.java | 58 ++++---- .../network/global/ChatOrderVerification.java | 13 -- .../modules/network/global/Keepalive.java | 13 -- .../PremiumAccountSlowLoginTimeout.java | 13 -- .../opt/CheckSurvivalBeforeGrowth.java | 24 ++-- .../leaf/config/modules/opt/DespawnTime.java | 31 ++--- .../config/modules/opt/DontSaveEntity.java | 35 +++-- .../modules/opt/DynamicActivationofBrain.java | 96 +++++++------ .../opt/EnableCachedMTBEntityTypeConvert.java | 23 ++-- .../leaf/config/modules/opt/EntityGoal.java | 45 ++++--- .../FasterStructureGenFutureSequencing.java | 23 ++-- .../dreeam/leaf/config/util/ConfigPaths.java | 4 +- .../src/main/java/su/plo/matter/Globals.java | 2 +- 65 files changed, 799 insertions(+), 1048 deletions(-) create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/BookWriting.java delete mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/global/GameplayMechanics.java delete 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/Including5sInGetTPS.java delete mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/Including5sIngetTPS.java delete mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/SecureSeed.java delete 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/world/SecureSeed.java create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/ChatOrderVerification.java create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/Keepalive.java create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/PremiumAccountSlowLoginTimeout.java delete mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/global/ChatOrderVerification.java delete mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/global/Keepalive.java delete mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/global/PremiumAccountSlowLoginTimeout.java diff --git a/leaf-server/minecraft-patches/features/0326-Leaf-config-changes.patch b/leaf-server/minecraft-patches/features/0326-Leaf-config-changes.patch index 4b968b0352..6de0b55b4e 100644 --- a/leaf-server/minecraft-patches/features/0326-Leaf-config-changes.patch +++ b/leaf-server/minecraft-patches/features/0326-Leaf-config-changes.patch @@ -247,7 +247,7 @@ index 118f4df4599768488d5fd86e97b0b024bea00365..c7eed64b8446dca61c134ed18283ceb1 + // 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"))) { ++ if (!(org.dreeam.leaf.config.modules.gameplay.global.BookWriting.enableBookWriting || craftPlayer.hasPermission(org.bukkit.craftbukkit.util.permissions.CraftDefaultPermissions.writeBooks) || craftPlayer.hasPermission("pufferfish.usebooks"))) { return; } - // Gale end - Pufferfish - make book writing configurable diff --git a/leaf-server/paper-patches/features/0019-Including-5s-in-getTPS.patch b/leaf-server/paper-patches/features/0019-Including-5s-in-getTPS.patch index 87e5da7e16..ef12d525b3 100644 --- a/leaf-server/paper-patches/features/0019-Including-5s-in-getTPS.patch +++ b/leaf-server/paper-patches/features/0019-Including-5s-in-getTPS.patch @@ -12,7 +12,7 @@ index c9dccaa61c6b216ae9c827b8eed5d7a21cccef15..59392b5a240ebee6517670e10e637ad0 @Override public double[] getTPS() { -+ if (org.dreeam.leaf.config.modules.misc.Including5sIngetTPS.enabled) return getTPSIncluding5SecondAverage(); // Leaf - Including 5s in getTPS() ++ if (org.dreeam.leaf.config.modules.misc.Including5sInGetTPS.enabled) return getTPSIncluding5SecondAverage(); // Leaf - Including 5s in getTPS() return this.getServer().getTPS(); } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/path/PathfindTaskRejectPolicy.java b/leaf-server/src/main/java/org/dreeam/leaf/async/path/PathfindTaskRejectPolicy.java index 4e195f440e..c69186f7c6 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/path/PathfindTaskRejectPolicy.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/path/PathfindTaskRejectPolicy.java @@ -12,8 +12,8 @@ public static PathfindTaskRejectPolicy fromString(String policy) { try { return PathfindTaskRejectPolicy.valueOf(policy.toUpperCase(Locale.ROOT)); } catch (IllegalArgumentException e) { - LeafConfig.LOGGER.warn("Invalid pathfind task reject policy: {}, falling back to {}.", policy, FLUSH_ALL.toString()); - return FLUSH_ALL; + LeafConfig.LOGGER.warn("Invalid pathfind task reject policy: {}, falling back to {}.", policy, CALLER_RUNS.toString()); + return CALLER_RUNS; } } } 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 333c23ee5c..29c7c05413 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 @@ -10,6 +10,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dreeam.leaf.config.annotations.Experimental; +import org.dreeam.leaf.config.annotations.HotReloadUnsupported; import org.dreeam.leaf.config.migration.LeafConfigMigration; import org.dreeam.leaf.config.migration.gale.GaleConfigMigration; import org.dreeam.leaf.config.modules.misc.SentryDSN; @@ -169,7 +170,7 @@ public static void loadConfig() { for (ConfigModule module : GLOBAL_MODULES) { ConfigBinder.bind(module, null, globalConfig, true); } - runGlobalModuleCallbacks(); + runGlobalModuleCallbacks(false); worldDefaultsConfig = loadWorldDefaults(worldDefaultsFile); worldDefaultsConfig.saveConfig(); @@ -230,14 +231,18 @@ private static void discoverGlobalModules() throws ReflectiveOperationException } } - private static void runGlobalModuleCallbacks() throws IllegalAccessException { + private static void runGlobalModuleCallbacks(boolean reload) throws IllegalAccessException { List enabledExperimentalModules = new ArrayList<>(); List deprecatedModules = new ArrayList<>(); for (ConfigModule module : GLOBAL_MODULES) { + Class moduleClass = module.getClass(); + if (reload && moduleClass.isAnnotationPresent(HotReloadUnsupported.class)) { + continue; + } + module.onLoaded(); - Class moduleClass = module.getClass(); for (Field field : moduleClass.getDeclaredFields()) { boolean experimental = field.isAnnotationPresent(Experimental.class); boolean deprecated = field.isAnnotationPresent(Deprecated.class); @@ -299,8 +304,8 @@ private static void commitReload( appliedValues++; } - runGlobalModuleCallbacks(); - runAfterBootstrapCallbacks(); + runGlobalModuleCallbacks(true); + runAfterBootstrapCallbacks(true); ConfigFileIO.saveAtomically(worldDefaultsConfig.configFile, globalConfig.configFile); } catch (Exception exception) { for (int index = appliedValues - 1; index >= 0; index--) { @@ -317,12 +322,12 @@ private static void commitReload( } try { - runGlobalModuleCallbacks(); + runGlobalModuleCallbacks(true); } catch (Exception callbackException) { exception.addSuppressed(callbackException); } try { - runAfterBootstrapCallbacks(); + runAfterBootstrapCallbacks(true); } catch (Exception callbackException) { exception.addSuppressed(callbackException); } @@ -375,7 +380,7 @@ private static void applyWorldOverride( } public static void loadAfterBootstrap() { - runAfterBootstrapCallbacks(); + runAfterBootstrapCallbacks(false); try { globalConfig.saveConfig(); @@ -384,8 +389,11 @@ public static void loadAfterBootstrap() { } } - private static void runAfterBootstrapCallbacks() { + private static void runAfterBootstrapCallbacks(boolean reload) { for (ConfigModule module : GLOBAL_MODULES) { + if (reload && module.getClass().isAnnotationPresent(HotReloadUnsupported.class)) { + continue; + } module.onRegistriesLoaded(); } } 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 b2c6514ffa..ff1d910acf 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 @@ -6,6 +6,7 @@ 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.misc.world.SecureSeed; 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; @@ -42,8 +43,7 @@ enum Source { public EntityWakeUpDuration entityWakeUpDuration = new EntityWakeUpDuration(); public HideFlamesOnEntitiesWithFireResistance hideFlamesOnEntitiesWithFireResistance = new HideFlamesOnEntitiesWithFireResistance(); public EnderDragonRespawn enderDragonRespawn = new EnderDragonRespawn(); - - public boolean secureSeedEnabled; + public SecureSeed secureSeed = new SecureSeed(); LeafWorldConfig(ConfigFile configFile, Source source) { super(configFile); 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 ee974fff38..c88165cb64 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 @@ -15,7 +15,7 @@ ConfigCategory category(); - String name(); + String name() default ""; /** * Optional section comment. Text blocks are supported. diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPathfinding.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPathfinding.java index 9afbbcdd82..86ec75d26c 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPathfinding.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPathfinding.java @@ -30,7 +30,7 @@ public class AsyncPathfinding implements ConfigModule { FLUSH_ALL: 所有等待中的任务都将在主线程上运行. CALLER_RUNS: 新提交的任务将在主线程上运行.""" }) - public static String rejectPolicy = "CALLER_RUNS"; + public static String rejectPolicy = "default"; public static @DoNotLoad PathfindTaskRejectPolicy rejectPolicyType = PathfindTaskRejectPolicy.CALLER_RUNS; @@ -50,11 +50,15 @@ public void onLoaded() { queueSize = maxThreads * 256; } - rejectPolicyType = PathfindTaskRejectPolicy.fromString(globalConfig.getString(basePath() + ".reject-policy", - availableProcessors >= 12 && queueSize < 512 + String policyStr = rejectPolicy; + + if ("default".equalsIgnoreCase(policyStr)) { + policyStr = availableProcessors >= 12 && queueSize < 512 ? PathfindTaskRejectPolicy.FLUSH_ALL.toString() - : PathfindTaskRejectPolicy.CALLER_RUNS.toString()) - ); + : PathfindTaskRejectPolicy.CALLER_RUNS.toString(); + } + + rejectPolicyType = PathfindTaskRejectPolicy.fromString(policyStr); if (enabled) { LeafConfig.LOGGER.info("Using {} threads for Async Pathfinding", maxThreads); diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/fixes/MCBugFix.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/fixes/MCBugFix.java index acf24c52de..12dbd31586 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/fixes/MCBugFix.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/fixes/MCBugFix.java @@ -2,58 +2,55 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; -public class MCBugFix extends ConfigModule { - - public String basePath() { - return ConfigCategory.FIXES.basePath() + ".vanilla-bug-fix"; - } +@ConfigClassInfo(category = ConfigCategory.FIXES, name = "vanilla-bug-fix", comments = { + "Fixes for vanilla Minecraft bugs.", + "针对 Minecraft 原版的漏洞修复。" +}) +public class MCBugFix implements ConfigModule { public static final Logger LOGGER = LogManager.getLogger("Leaf Vanilla Bug Fix"); + @ConfigInfo(name = "mc-152094", comments = { + """ + Whether to fix the bug End City ship generation gets cut at chunk borders. + Mojira link: https://mojira.dev/MC-152094""", + """ + 是否修复末地船在区块边缘生成时被截断的问题。 + 漏洞跟踪器链接:https://mojira.dev/MC-152094""" + }) + public static boolean mc152094 = false; + + @ConfigInfo(name = "mc-270656", comments = { + """ + Whether to fix incorrect granting of 'Who needs rockets?' advancement. + Mojira link: https://mojira.dev/MC-270656""", + """ + 是否修复“还要啥火箭啊?”进度触发的错误检查逻辑。 + 漏洞跟踪器链接:https://mojira.dev/MC-270656""" + }) public static boolean mc270656 = false; + + @ConfigInfo(name = "mc-301114", comments = { + """ + Whether to fix the memory leak in the combat tracker caused by the mob constantly being damaged. + Mojira link: https://mojira.dev/MC-301114""", + """ + 是否修复战斗跟踪器(Combat Tracker)中,因生物受到持续性伤害导致的内存泄漏。 + 漏洞跟踪器链接:https://mojira.dev/MC-301114""" + }) public static boolean mc301114 = false; + + @ConfigInfo(name = "mc-301114-max-entries", comments = { + "Max allowed entries in mob's combat tracker.", + "生物战斗跟踪器中允许的最大条目。" + }) public static int mc301114maxCombatEntries = 10240; - public static boolean mc152094 = false; @Override public void onLoaded() { - globalConfig.addCommentRegionBased(basePath(), - "Fixes for vanilla Minecraft bugs.", - "针对 Minecraft 原版的漏洞修复。" - ); - mc270656 = globalConfig.getBoolean(basePath() + ".mc-270656", mc270656, globalConfig.pickStringRegionBased( - """ - Whether to fix incorrect granting of 'Who needs rockets?' advancement. - Mojira link: https://mojira.dev/MC-270656""", - """ - 是否修复“还要啥火箭啊?”进度触发的错误检查逻辑。 - 漏洞跟踪器链接:https://mojira.dev/MC-270656""" - )); - mc301114 = globalConfig.getBoolean(basePath() + ".mc-301114", mc301114, globalConfig.pickStringRegionBased( - """ - Whether to fix the memory leak in the combat tracker caused by the mob constantly being damaged. - Mojira link: https://mojira.dev/MC-301114""", - """ - 是否修复战斗跟踪器(Combat Tracker)中,因生物受到持续性伤害导致的内存泄漏。 - 漏洞跟踪器链接:https://mojira.dev/MC-301114""" - )); - mc301114maxCombatEntries = globalConfig.getInt(basePath() + ".mc-301114-max-entries", mc301114maxCombatEntries, globalConfig.pickStringRegionBased( - "Max allowed entries in mob's combat tracker.", - "生物战斗跟踪器中允许的最大条目。" - )); - mc301114maxCombatEntries = Math.max(1, mc301114maxCombatEntries); - - mc152094 = globalConfig.getBoolean(basePath() + ".mc-152094", mc152094, globalConfig.pickStringRegionBased( - """ - Whether to fix the bug End City ship generation gets cut at chunk borders. - Mojira link: https://mojira.dev/MC-152094""", - """ - 是否修复末地船在区块边缘生成时被截断的问题。 - 漏洞跟踪器链接:https://mojira.dev/MC-152094""" - )); } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/fixes/PreventMoveIntoWeakLoadedChunks.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/fixes/PreventMoveIntoWeakLoadedChunks.java index 54aa015ec5..46f8ff1ed0 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/fixes/PreventMoveIntoWeakLoadedChunks.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/fixes/PreventMoveIntoWeakLoadedChunks.java @@ -1,36 +1,26 @@ package org.dreeam.leaf.config.modules.fixes; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; - -public class PreventMoveIntoWeakLoadedChunks extends ConfigModule { - - public String basePath() { - return ConfigCategory.FIXES.basePath() + ".prevent-moving-into-weak-loaded-chunks"; - } - +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; + +@ConfigClassInfo(category = ConfigCategory.FIXES, name = "prevent-moving-into-weak-loaded-chunks", comments = { + "Prevents entities from moving into weak loaded chunks.", + "阻止实体进入弱加载区块。" +}) +public class PreventMoveIntoWeakLoadedChunks implements ConfigModule { + + @ConfigInfo(name = "enabled", comments = { + "Set to true to enable features below.", + "设置为 true 以启用以下功能。" + }) public static boolean enabled = false; + @ConfigInfo(name = "projectiles", comments = { + "Prevents projectiles from moving into weak loaded chunks.", + "阻止弹射物进入弱加载区块。" + }) public static boolean projectiles = false; public static boolean isProjectileEnabled() { return enabled && projectiles; } - - @Override - public void onLoaded() { - globalConfig.addCommentRegionBased(basePath(), - "Prevents entities from moving into weak loaded chunks.", - "阻止实体进入弱加载区块。" - ); - - enabled = globalConfig.getBoolean(basePath() + ".enabled", enabled, globalConfig.pickStringRegionBased( - "Set to true to enable features below.", - "设置为 true 以启用以下功能。" - )); - - projectiles = globalConfig.getBoolean(basePath() + ".projectiles", projectiles, globalConfig.pickStringRegionBased( - "Prevents projectiles from moving into weak loaded chunks.", - "阻止弹射物进入弱加载区块。" - )); - } } 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 index dd2a39358b..eca965c1f0 100644 --- 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 @@ -1,9 +1,7 @@ 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; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; @ConfigClassInfo(category = ConfigCategory.FIXES, name = "gameplay-fixes") public final class Fixes implements WorldConfigModule { diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/AfkCommand.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/AfkCommand.java index 8832f22cac..7b9c4affe0 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/AfkCommand.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/AfkCommand.java @@ -1,23 +1,18 @@ package org.dreeam.leaf.config.modules.gameplay; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; -public class AfkCommand extends ConfigModule { - - public String basePath() { - return ConfigCategory.GAMEPLAY.basePath() + ".afk-command"; - } +@ConfigClassInfo(category = ConfigCategory.GAMEPLAY, name = "afk-command") +public class AfkCommand implements ConfigModule { + @ConfigInfo(name = "enabled", comments = { + """ + The AFK command based on Minecraft built-in idle-timeout mechanism + Rest of AFK settings are in the Purpur config""", + """ + 基于原版 idle-timeout 系统的 AFK 指令 + 剩余配置项在 Purpur 配置里""" + }) public static boolean enabled = false; - - @Override - public void onLoaded() { - enabled = globalConfig.getBoolean(basePath() + ".enabled", enabled, globalConfig.pickStringRegionBased(""" - The AFK command based on Minecraft built-in idle-timeout mechanism - Rest of AFK settings are in the Purpur config""", - """ - 基于原版 idle-timeout 系统的 AFK 指令 - 剩余配置项在 Purpur 配置里""")); - } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/BookWriting.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/BookWriting.java new file mode 100644 index 0000000000..4a26c8ed61 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/BookWriting.java @@ -0,0 +1,11 @@ +package org.dreeam.leaf.config.modules.gameplay; + +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; + +@ConfigClassInfo(category = ConfigCategory.GAMEPLAY, name = "book-writing") +public final class BookWriting implements ConfigModule { + + @ConfigInfo(name = "enabled") + public static boolean enabled = true; +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/ConfigurableInventoryOverflowEvent.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/ConfigurableInventoryOverflowEvent.java index 4cc1d5f667..4eb87903f2 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/ConfigurableInventoryOverflowEvent.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/ConfigurableInventoryOverflowEvent.java @@ -1,32 +1,28 @@ package org.dreeam.leaf.config.modules.gameplay; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; -public class ConfigurableInventoryOverflowEvent extends ConfigModule { - - public String basePath() { - return ConfigCategory.GAMEPLAY.basePath() + ".inventory-overflow-event"; - } +@ConfigClassInfo(category = ConfigCategory.GAMEPLAY, name = "inventory-overflow-event") +public class ConfigurableInventoryOverflowEvent implements ConfigModule { + @ConfigInfo(name = "enabled", comments = { + """ + The event called when used plugin to Inventory#addItem + into player's inventory, and the inventory is full. + This is not recommended to use, please re-design to use the + returned map of Inventory#addItem method as soon as possible!""", + """ + 此事件将在插件使用 Inventory#addItem 方法 + 添加物品到玩家背包, 但是背包已满时调用. + 不建议使用此事件,请尽快迁移至使用 Inventory#addItem 方法 + 返回的 map""" + }) public static boolean enabled = false; - public static String listenerClass = "com.example.package.PlayerInventoryOverflowEvent"; - @Override - public void onLoaded() { - enabled = globalConfig.getBoolean(basePath() + ".enabled", enabled, globalConfig.pickStringRegionBased(""" - The event called when used plugin to Inventory#addItem - into player's inventory, and the inventory is full. - This is not recommended to use, please re-design to use the - returned map of Inventory#addItem method as soon as possible!""", - """ - 此事件将在插件使用 Inventory#addItem 方法 - 添加物品到玩家背包, 但是背包已满时调用. - 不建议使用此事件,请尽快迁移至使用 Inventory#addItem 方法 - 返回的 map""")); - listenerClass = globalConfig.getString(basePath() + ".listener-class", listenerClass, globalConfig.pickStringRegionBased(""" - The full class name of the listener which listens to this inventory overflow event.""", - """ - 监听此物品栏物品溢出事件的完整类名.""")); - } + @ConfigInfo(name = "listener-class", comments = { + "The full class name of the listener which listens to this inventory overflow event.", + "监听此物品栏物品溢出事件的完整类名." + }) + public static String listenerClass = "com.example.package.PlayerInventoryOverflowEvent"; } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/ConfigurableMaxUseItemDistance.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/ConfigurableMaxUseItemDistance.java index 711deab050..aa9eeed1e4 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/ConfigurableMaxUseItemDistance.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/ConfigurableMaxUseItemDistance.java @@ -1,28 +1,23 @@ package org.dreeam.leaf.config.modules.gameplay; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; -public class ConfigurableMaxUseItemDistance extends ConfigModule { - - public String basePath() { - return ConfigCategory.GAMEPLAY.basePath() + ".player"; - } +@ConfigClassInfo(category = ConfigCategory.GAMEPLAY, name = "player") +public class ConfigurableMaxUseItemDistance implements ConfigModule { + @ConfigInfo(name = "max-use-item-distance", comments = { + """ + The max distance of UseItem for players. + Set to -1 to disable max-distance-check. + NOTE: if set to -1 to disable the check, + players are able to use some packet modules of hack clients, + and NoCom Exploit!!""", + """ + 玩家 UseItem 的最大距离. + 设置为 -1 来禁用最大距离检测. + 注意: 禁用此项后, + 玩家可以使用作弊客户端的部分发包模块和 NoCom 漏洞!!""" + }) public static double maxUseItemDistance = 1.0000001; - - @Override - public void onLoaded() { - maxUseItemDistance = globalConfig.getDouble(basePath() + ".max-use-item-distance", maxUseItemDistance, globalConfig.pickStringRegionBased(""" - The max distance of UseItem for players. - Set to -1 to disable max-distance-check. - NOTE: if set to -1 to disable the check, - players are able to use some packet modules of hack clients, - and NoCom Exploit!!""", - """ - 玩家 UseItem 的最大距离. - 设置为 -1 来禁用最大距离检测. - 注意: 禁用此项后, - 玩家可以使用作弊客户端的部分发包模块和 NoCom 漏洞!!""")); - } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/ConfigurableTripWireDupe.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/ConfigurableTripWireDupe.java index c7e7a75518..846e7ca106 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/ConfigurableTripWireDupe.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/ConfigurableTripWireDupe.java @@ -1,18 +1,11 @@ package org.dreeam.leaf.config.modules.gameplay; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; -public class ConfigurableTripWireDupe extends ConfigModule { - - public String basePath() { - return ConfigCategory.GAMEPLAY.basePath(); - } +@ConfigClassInfo(category = ConfigCategory.GAMEPLAY) +public class ConfigurableTripWireDupe implements ConfigModule { + @ConfigInfo(name = "allow-tripwire-dupe") public static boolean enabled = false; - - @Override - public void onLoaded() { - enabled = globalConfig.getBoolean(basePath() + ".allow-tripwire-dupe", enabled); - } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/DeathItemDropKnockback.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/DeathItemDropKnockback.java index b5cec8e94c..52560b101f 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/DeathItemDropKnockback.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/DeathItemDropKnockback.java @@ -1,36 +1,26 @@ package org.dreeam.leaf.config.modules.gameplay; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; -public class DeathItemDropKnockback extends ConfigModule { - - public String basePath() { - return ConfigCategory.GAMEPLAY.basePath() + ".death-item-drop-knockback"; - } +@ConfigClassInfo(category = ConfigCategory.GAMEPLAY, name = "death-item-drop-knockback") +public class DeathItemDropKnockback implements ConfigModule { + @ConfigInfo(name = "drop-around", comments = { + "If true, items will drop randomly around the player on death.", + "如果为 “true”,物品会在玩家死亡时随机掉落在其周围." + }) public static boolean dropAround = true; - public static double horizontalForce = 0.5; - public static double verticalForce = 0.2; - @Override - public void onLoaded() { - dropAround = globalConfig.getBoolean(basePath() + ".drop-around", dropAround, - globalConfig.pickStringRegionBased( - "If true, items will drop randomly around the player on death.", - "如果为 “true”,物品会在玩家死亡时随机掉落在其周围." - )); - - horizontalForce = globalConfig.getDouble(basePath() + ".horizontal-force", horizontalForce, - globalConfig.pickStringRegionBased( - "Base speed for horizontal velocity when randomly dropping items.", - "随机掉落物品时水平速度的基本速度." - )); + @ConfigInfo(name = "horizontal-force", comments = { + "Base speed for horizontal velocity when randomly dropping items.", + "随机掉落物品时水平速度的基本速度." + }) + public static double horizontalForce = 0.5; - verticalForce = globalConfig.getDouble(basePath() + ".vertical-force", verticalForce, - globalConfig.pickStringRegionBased( - "Upward motion for randomly dropped items.", - "随机掉落物品的向上运动." - )); - } + @ConfigInfo(name = "vertical-force", comments = { + "Upward motion for randomly dropped items.", + "随机掉落物品的向上运动." + }) + public static double verticalForce = 0.2; } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/IceAndSnowChance.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/IceAndSnowChance.java index 2038239217..6614113cdf 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/IceAndSnowChance.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/IceAndSnowChance.java @@ -1,19 +1,16 @@ package org.dreeam.leaf.config.modules.gameplay; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; -public class IceAndSnowChance extends ConfigModule { - - public String basePath() { - return ConfigCategory.GAMEPLAY.basePath() + ".ice-and-snow-chance"; - } +@ConfigClassInfo(category = ConfigCategory.GAMEPLAY) +public class IceAndSnowChance implements ConfigModule { + @ConfigInfo(name = "ice-and-snow-chance") public static int iceAndSnowChance = 48; @Override public void onLoaded() { - iceAndSnowChance = globalConfig.getInt(basePath(), iceAndSnowChance); if (iceAndSnowChance <= 0) { iceAndSnowChance = 48; } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/Knockback.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/Knockback.java index 28501c22a3..e785a052c3 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/Knockback.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/Knockback.java @@ -1,44 +1,35 @@ package org.dreeam.leaf.config.modules.gameplay; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; -import org.dreeam.leaf.config.annotations.Experimental; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; -public class Knockback extends ConfigModule { - - public String basePath() { - return ConfigCategory.GAMEPLAY.basePath() + ".knockback"; - } +@ConfigClassInfo(category = ConfigCategory.GAMEPLAY, name = "knockback") +public class Knockback implements ConfigModule { + @ConfigInfo(name = "snowball-knockback-players", comments = { + "Make snowball can knockback players.", + "使雪球可以击退玩家." + }) public static boolean snowballCanKnockback = false; + + @ConfigInfo(name = "egg-knockback-players", comments = { + "Make egg can knockback players.", + "使鸡蛋可以击退玩家." + }) public static boolean eggCanKnockback = false; + + @ConfigInfo(name = "can-player-knockback-zombie", comments = { + "Make players can knockback zombie.", + "使玩家可以击退僵尸." + }) public static boolean canPlayerKnockbackZombie = true; - @Experimental - public static boolean flushKnockback = false; - public static boolean oldBlastProtectionKnockbackBehavior = false; - @Override - public void onLoaded() { - snowballCanKnockback = globalConfig.getBoolean(basePath() + ".snowball-knockback-players", snowballCanKnockback, - globalConfig.pickStringRegionBased( - "Make snowball can knockback players.", - "使雪球可以击退玩家." - )); - eggCanKnockback = globalConfig.getBoolean(basePath() + ".egg-knockback-players", eggCanKnockback, - globalConfig.pickStringRegionBased( - "Make egg can knockback players.", - "使鸡蛋可以击退玩家." - )); - canPlayerKnockbackZombie = globalConfig.getBoolean(basePath() + ".can-player-knockback-zombie", canPlayerKnockbackZombie, - globalConfig.pickStringRegionBased( - "Make players can knockback zombie.", - "使玩家可以击退僵尸." - )); - flushKnockback = globalConfig.getBoolean(basePath() + ".flush-location-while-knockback-player", flushKnockback, - globalConfig.pickStringRegionBased( - "Synchronize player immediately when knocked back.", - "被击退时立即同步玩家." - )); - oldBlastProtectionKnockbackBehavior = globalConfig.getBoolean(basePath() + ".old-blast-protection-explosion-knockback", oldBlastProtectionKnockbackBehavior); - } + @ConfigInfo(name = "flush-location-while-knockback-player", comments = { + "Synchronize player immediately when knocked back.", + "被击退时立即同步玩家." + }) + public static @Experimental boolean flushKnockback = false; + + @ConfigInfo(name = "old-blast-protection-explosion-knockback") + public static boolean oldBlastProtectionKnockbackBehavior = false; } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/OnlyPlayerPushable.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/OnlyPlayerPushable.java index 56c8e1c56d..7aa021e986 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/OnlyPlayerPushable.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/OnlyPlayerPushable.java @@ -1,21 +1,14 @@ package org.dreeam.leaf.config.modules.gameplay; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; -public class OnlyPlayerPushable extends ConfigModule { - - public String basePath() { - return ConfigCategory.GAMEPLAY.basePath() + ".only-player-pushable"; - } +@ConfigClassInfo(category = ConfigCategory.GAMEPLAY) +public class OnlyPlayerPushable implements ConfigModule { + @ConfigInfo(name = "only-player-pushable", comments = { + "Enable to make only player pushable", + "是否只允许玩家被实体推动" + }) public static boolean enabled = false; - - @Override - public void onLoaded() { - enabled = globalConfig.getBoolean(basePath(), enabled, globalConfig.pickStringRegionBased( - "Enable to make only player pushable", - "是否只允许玩家被实体推动" - )); - } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/SpawnerSettings.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/SpawnerSettings.java index 534a8f3734..e88170314b 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/SpawnerSettings.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/SpawnerSettings.java @@ -1,93 +1,73 @@ package org.dreeam.leaf.config.modules.gameplay; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; - -public class SpawnerSettings extends ConfigModule { - - public String basePath() { - return ConfigCategory.GAMEPLAY.basePath() + ".spawner-settings"; - } - - // Global toggle +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; + +@ConfigClassInfo(category = ConfigCategory.GAMEPLAY, name = "spawner-settings", comments = { + "This section contains settings for mob spawner blocks.", + "此部分包含刷怪笼生物生成的设置." +}) +public class SpawnerSettings implements ConfigModule { + + @ConfigInfo(name = "enabled", comments = { + "Enable custom spawner settings. Set to true to enable all features below.", + "启用自定义刷怪笼设置. 设为 true 以启用以下所有功能." + }) public static boolean enabled = false; - // Default values for spawner settings + @ConfigInfo(name = "checks.light-level-check", comments = { + "Check if there is the required light level to spawn the mob", + "检查是否有所需的光照等级来生成怪物" + }) public static boolean lightLevelCheck = false; + + @ConfigInfo(name = "checks.spawner-max-nearby-check", comments = { + "Check if there are the max amount of nearby mobs to spawn the mob", + "检查附近是否已达到最大怪物数量限制" + }) public static boolean spawnerMaxNearbyCheck = true; + + @ConfigInfo(name = "checks.check-for-nearby-players", comments = { + "Check if any players are in a radius to spawn the mob", + "检查是否有玩家在生成怪物的半径范围内" + }) public static boolean checkForNearbyPlayers = true; + + @ConfigInfo(name = "checks.spawner-block-checks", comments = { + "Check if there are physical blocks obstructing the spawn location, or if custom spawn rules (isValidPosition) fail due to block conditions.", + "检查是否有物理方块阻挡生成位置, 或自定义生成规则(isValidPosition)因方块条件失败." + }) public static boolean spawnerBlockChecks = false; + + @ConfigInfo(name = "checks.water-prevent-spawn-check", comments = { + "Checks if there is water around that prevents spawning", + "检查周围是否有水阻止生成" + }) public static boolean waterPreventSpawnCheck = false; + + @ConfigInfo(name = "checks.ignore-spawn-rules", comments = { + "Ignore mob-specific spawn rules, like animals needing grass or specific biomes/blocks (does not affect light level or physical obstruction checks).", + "忽略特定于生物的生成规则, 例如动物需要草方块或特定的生物群系/方块 (不影响光照等级或物理障碍物检查)." + }) public static boolean ignoreSpawnRules = false; + @ConfigInfo(name = "min-spawn-delay", comments = { + "Minimum delay (in ticks) between spawner spawns. Higher values slow down spawners.", + "刷怪笼生成怪物之间的最小延迟 (以刻为单位). 较高的值会减缓刷怪笼的速度." + }) public static int minSpawnDelay = 200; + + @ConfigInfo(name = "max-spawn-delay", comments = { + "Maximum delay (in ticks) between spawner spawns. Higher values slow down spawners.", + "刷怪笼生成怪物之间的最大延迟 (以刻为单位). 较高的值会减缓刷怪笼的速度." + }) public static int maxSpawnDelay = 800; @Override public void onLoaded() { - globalConfig.addCommentRegionBased(basePath(), - "This section contains settings for mob spawner blocks.", - "此部分包含刷怪笼生物生成的设置."); - - // Global toggle - enabled = globalConfig.getBoolean(basePath() + ".enabled", enabled, - globalConfig.pickStringRegionBased( - "Enable custom spawner settings. Set to true to enable all features below.", - "启用自定义刷怪笼设置. 设为 true 以启用以下所有功能." - )); - - // Checks section globalConfig.addCommentRegionBased(basePath() + ".checks", "Various checks that can be enabled or disabled for spawner blocks.", - "可以为刷怪笼启用或禁用的各种检查."); - - lightLevelCheck = globalConfig.getBoolean(basePath() + ".checks.light-level-check", lightLevelCheck, - globalConfig.pickStringRegionBased( - "Check if there is the required light level to spawn the mob", - "检查是否有所需的光照等级来生成怪物" - )); - - spawnerMaxNearbyCheck = globalConfig.getBoolean(basePath() + ".checks.spawner-max-nearby-check", spawnerMaxNearbyCheck, - globalConfig.pickStringRegionBased( - "Check if there are the max amount of nearby mobs to spawn the mob", - "检查附近是否已达到最大怪物数量限制" - )); - - checkForNearbyPlayers = globalConfig.getBoolean(basePath() + ".checks.check-for-nearby-players", checkForNearbyPlayers, - globalConfig.pickStringRegionBased( - "Check if any players are in a radius to spawn the mob", - "检查是否有玩家在生成怪物的半径范围内" - )); - - spawnerBlockChecks = globalConfig.getBoolean(basePath() + ".checks.spawner-block-checks", spawnerBlockChecks, - globalConfig.pickStringRegionBased( - "Check if there are physical blocks obstructing the spawn location, or if custom spawn rules (isValidPosition) fail due to block conditions.", - "检查是否有物理方块阻挡生成位置, 或自定义生成规则(isValidPosition)因方块条件失败." - )); - - waterPreventSpawnCheck = globalConfig.getBoolean(basePath() + ".checks.water-prevent-spawn-check", waterPreventSpawnCheck, - globalConfig.pickStringRegionBased( - "Checks if there is water around that prevents spawning", - "检查周围是否有水阻止生成" - )); - ignoreSpawnRules = globalConfig.getBoolean(basePath() + ".checks.ignore-spawn-rules", ignoreSpawnRules, - globalConfig.pickStringRegionBased( - "Ignore mob-specific spawn rules, like animals needing grass or specific biomes/blocks (does not affect light level or physical obstruction checks).", - "忽略特定于生物的生成规则, 例如动物需要草方块或特定的生物群系/方块 (不影响光照等级或物理障碍物检查)." - )); - - // Delay settings - - minSpawnDelay = globalConfig.getInt(basePath() + ".min-spawn-delay", minSpawnDelay, - globalConfig.pickStringRegionBased( - "Minimum delay (in ticks) between spawner spawns. Higher values slow down spawners.", - "刷怪笼生成怪物之间的最小延迟 (以刻为单位). 较高的值会减缓刷怪笼的速度." - )); - - maxSpawnDelay = globalConfig.getInt(basePath() + ".max-spawn-delay", maxSpawnDelay, - globalConfig.pickStringRegionBased( - "Maximum delay (in ticks) between spawner spawns. Higher values slow down spawners.", - "刷怪笼生成怪物之间的最大延迟 (以刻为单位). 较高的值会减缓刷怪笼的速度." - )); + "可以为刷怪笼启用或禁用的各种检查." + ); } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/UseSpigotItemMergingMech.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/UseSpigotItemMergingMech.java index 743fffa57a..1024692f06 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/UseSpigotItemMergingMech.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/UseSpigotItemMergingMech.java @@ -1,18 +1,11 @@ package org.dreeam.leaf.config.modules.gameplay; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; -public class UseSpigotItemMergingMech extends ConfigModule { - - public String basePath() { - return ConfigCategory.GAMEPLAY.basePath() + ".use-spigot-item-merging-mechanism"; - } +@ConfigClassInfo(category = ConfigCategory.GAMEPLAY) +public class UseSpigotItemMergingMech implements ConfigModule { + @ConfigInfo(name = "use-spigot-item-merging-mechanism") public static boolean enabled = false; - - @Override - public void onLoaded() { - enabled = globalConfig.getBoolean(basePath(), enabled); - } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/VanillaHopper.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/VanillaHopper.java index f37574135f..6c237a11db 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/VanillaHopper.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/VanillaHopper.java @@ -1,18 +1,11 @@ package org.dreeam.leaf.config.modules.gameplay; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; -public class VanillaHopper extends ConfigModule { - - public String basePath() { - return ConfigCategory.GAMEPLAY.basePath() + ".use-vanilla-hopper"; - } +@ConfigClassInfo(category = ConfigCategory.GAMEPLAY) +public class VanillaHopper implements ConfigModule { + @ConfigInfo(name = "use-vanilla-hopper") public static boolean enabled = false; - - @Override - public void onLoaded() { - enabled = globalConfig.getBoolean(basePath(), enabled); - } } 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 deleted file mode 100644 index fffacd7240..0000000000 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/global/GameplayMechanics.java +++ /dev/null @@ -1,13 +0,0 @@ -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 index aa9b3360a7..7bb065a31b 100644 --- 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 @@ -1,9 +1,7 @@ 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; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; @ConfigClassInfo(category = ConfigCategory.GAMEPLAY, name = "ender-dragon-respawn") public final class EnderDragonRespawn implements WorldConfigModule { 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 index 34f6dcd8ac..28ab1cfe74 100644 --- 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 @@ -1,9 +1,7 @@ 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; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; @ConfigClassInfo(category = ConfigCategory.GAMEPLAY, name = "hide-flames-on-entities-with-fire-resistance") public final class HideFlamesOnEntitiesWithFireResistance implements WorldConfigModule { 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 index b178bc8e0b..434e7df13d 100644 --- 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 @@ -1,9 +1,7 @@ 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; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; @ConfigClassInfo(category = ConfigCategory.GAMEPLAY, name = "random-stroll-into-non-ticking-chunks") public final class RandomStrollIntoNonTickingChunks implements WorldConfigModule { diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/ConnectionMessage.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/ConnectionMessage.java index d7aa3b97af..ae16a9443f 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/ConnectionMessage.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/ConnectionMessage.java @@ -1,43 +1,40 @@ package org.dreeam.leaf.config.modules.misc; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; - -public class ConnectionMessage extends ConfigModule { - - public String basePath() { - return ConfigCategory.MISC.basePath() + ".connection-message"; - } - +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; + +@ConfigClassInfo(category = ConfigCategory.MISC, name = "connection-message", comments = { + """ + Connection message, using MiniMessage format, set to "default" to use vanilla join message. + available placeholders: + - player name + - player display name""", + """ + 自定义加入 & 退出消息 (MiniMessage 格式), 设置为 'default' 将使用原版消息. + 可用的内置变量: + - 玩家名称 + - 玩家显示名称""" +}) +public class ConnectionMessage implements ConfigModule { + + @ConfigInfo(name = "join.enabled") public static boolean joinEnabled = true; + @ConfigInfo(name = "join.message", comments = { + "Join message of player", + "玩家加入服务器时的消息" + }) public static String joinMessage = "default"; + + @ConfigInfo(name = "quit.enabled") public static boolean quitEnabled = true; + @ConfigInfo(name = "quit.message", comments = { + "Quit message of player", + "玩家退出服务器时的消息" + }) public static String quitMessage = "default"; @Override public void onLoaded() { - globalConfig.addCommentRegionBased(basePath(), """ - Connection message, using MiniMessage format, set to "default" to use vanilla join message. - available placeholders: - - player name - - player display name""", - """ - 自定义加入 & 退出消息 (MiniMessage 格式), 设置为 'default' 将使用原版消息. - 可用的内置变量: - - 玩家名称 - - 玩家显示名称"""); - - joinEnabled = globalConfig.getBoolean(basePath() + ".join.enabled", joinEnabled); - joinMessage = globalConfig.getString(basePath() + ".join.message", joinMessage, globalConfig.pickStringRegionBased( - "Join message of player", - "玩家加入服务器时的消息" - )); - - quitEnabled = globalConfig.getBoolean(basePath() + ".quit.enabled", quitEnabled); - quitMessage = globalConfig.getString(basePath() + ".quit.message", quitMessage, globalConfig.pickStringRegionBased( - "Quit message of player", - "玩家退出服务器时的消息")); - // Legacy compatibility // TODO: config migration joinMessage = joinMessage diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/DisableWorldDataSaving.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/DisableWorldDataSaving.java index 36b790b7d1..040e252446 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/DisableWorldDataSaving.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/DisableWorldDataSaving.java @@ -1,31 +1,25 @@ package org.dreeam.leaf.config.modules.misc; import net.minecraft.server.level.ServerLevel; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; import java.util.ArrayList; import java.util.List; -public class DisableWorldDataSaving extends ConfigModule { +@ConfigClassInfo(category = ConfigCategory.MISC, name = "disable-world-data-saving") +public class DisableWorldDataSaving implements ConfigModule { + @ConfigInfo(name = "worlds", comments = { + """ + Worlds listed here will skip world data persistence. + Changes in chunks/entities remain in memory until unload/restart and are not written to disk.""", + """ + 此处列出的世界将跳过世界数据持久化。 + 区块/实体更改仅保留在内存中直到卸载或重启,不会写入磁盘。""" + }) public static List worlds = new ArrayList<>(); - public String basePath() { - return ConfigCategory.MISC.basePath() + ".disable-world-data-saving"; - } - - @Override - public void onLoaded() { - worlds = globalConfig.getList(basePath() + ".worlds", worlds, - globalConfig.pickStringRegionBased(""" - Worlds listed here will skip world data persistence. - Changes in chunks/entities remain in memory until unload/restart and are not written to disk.""", - """ - 此处列出的世界将跳过世界数据持久化。 - 区块/实体更改仅保留在内存中直到卸载或重启,不会写入磁盘。""")); - } - public static boolean shouldSkipSave(ServerLevel serverLevel) { return !worlds.isEmpty() && worlds.contains(serverLevel.getWorld().getName()); } 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 deleted file mode 100644 index 521e74724a..0000000000 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/GlobalConfigExample.java +++ /dev/null @@ -1,36 +0,0 @@ -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/Including5sInGetTPS.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/Including5sInGetTPS.java new file mode 100644 index 0000000000..e5f633053e --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/Including5sInGetTPS.java @@ -0,0 +1,11 @@ +package org.dreeam.leaf.config.modules.misc; + +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; + +@ConfigClassInfo(category = ConfigCategory.MISC) +public class Including5sInGetTPS implements ConfigModule { + + @ConfigInfo(name = "including-5s-in-get-tps") + public static boolean enabled = true; +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/Including5sIngetTPS.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/Including5sIngetTPS.java deleted file mode 100644 index 6d01c04988..0000000000 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/Including5sIngetTPS.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.dreeam.leaf.config.modules.misc; - -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; - -public class Including5sIngetTPS extends ConfigModule { - - public String basePath() { - return ConfigCategory.MISC.basePath(); - } - - public static boolean enabled = true; - - @Override - public void onLoaded() { - enabled = globalConfig.getBoolean(basePath() + ".including-5s-in-get-tps", enabled); - } -} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/LagCompensation.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/LagCompensation.java index 07b6e46e84..38f0ef2295 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/LagCompensation.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/LagCompensation.java @@ -1,29 +1,24 @@ package org.dreeam.leaf.config.modules.misc; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; -public class LagCompensation extends ConfigModule { - - public String basePath() { - return ConfigCategory.MISC.basePath() + ".lag-compensation"; - } +@ConfigClassInfo(category = ConfigCategory.MISC, name = "lag-compensation", comments = { + """ + This section contains lag compensation features, + which could ensure basic playing experience during a lag.""", + """ + 这部分包含滞后补偿功能, + 可以在卡顿情况下保障基本游戏体验.""" +}) +public class LagCompensation implements ConfigModule { + @ConfigInfo(name = "enabled") public static boolean enabled = false; - public static boolean enableForWater = false; - public static boolean enableForLava = false; - @Override - public void onLoaded() { - globalConfig.addCommentRegionBased(basePath(), """ - This section contains lag compensation features, - which could ensure basic playing experience during a lag.""", - """ - 这部分包含滞后补偿功能, - 可以在卡顿情况下保障基本游戏体验."""); + @ConfigInfo(name = "enable-for-water") + public static boolean enableForWater = false; - enabled = globalConfig.getBoolean(basePath() + ".enabled", enabled); - enableForWater = globalConfig.getBoolean(basePath() + ".enable-for-water", enableForWater); - enableForLava = globalConfig.getBoolean(basePath() + ".enable-for-lava", enableForLava); - } + @ConfigInfo(name = "enable-for-lava") + public static boolean enableForLava = false; } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/RegionFormatConfig.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/RegionFormatConfig.java index 27d42aef24..42cd9bfc71 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/RegionFormatConfig.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/RegionFormatConfig.java @@ -3,80 +3,71 @@ import abomination.LinearRegionFile; import me.earthme.luminol.enums.EnumRegionFormat; import me.earthme.luminol.utils.BufferedLinearRegionFileFlusher; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; -import org.dreeam.leaf.config.annotations.DoNotLoad; -import org.dreeam.leaf.config.annotations.HotReloadUnsupported; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; import org.dreeam.leaf.util.LeafConstants; -public class RegionFormatConfig extends ConfigModule { - - public String basePath() { - return ConfigCategory.MISC.basePath() + ".region-format"; - } - - public static @HotReloadUnsupported String regionFormatName = "MCA"; - public static @HotReloadUnsupported int compressionLevel = 6; - public static @HotReloadUnsupported int ioThreadCount = 6; - public static @HotReloadUnsupported int ioFlushDelay = -1; - public static @HotReloadUnsupported boolean linearUseVirtualThread = true; +@HotReloadUnsupported +@ConfigClassInfo(category = ConfigCategory.MISC, name = "region-format", comments = { + """ + Linear is a region format that uses zstd compression instead of zlib. + This format saves about 50% of disk space. + Read Leaf docs before using!""", + """ + Linear 是一种使用 zstd 压缩而非 ZLIB 的区域格式. + 该格式可节省约 50% 的磁盘空间. + 使用前请阅读 Leaf 文档!""" +}) +public class RegionFormatConfig implements ConfigModule { + + @ConfigInfo(name = "format-name", comments = { + "Available region format names: MCA, B_LINEAR, LINEAR_V2", + "可用格式: MCA, B_LINEAR, LINEAR_V2" + }) + public static String regionFormatName = "MCA"; + + @ConfigInfo(name = "compress-level") + public static int compressionLevel = 6; + + @ConfigInfo(name = "io-thread-count") + public static int ioThreadCount = 6; + + @ConfigInfo(name = "io-flush-delay") + public static int ioFlushDelay = -1; + + @ConfigInfo(name = "linear-use-virtual-thread") + public static boolean linearUseVirtualThread = true; public static @DoNotLoad EnumRegionFormat regionFormat = EnumRegionFormat.MCA; public static @DoNotLoad BufferedLinearRegionFileFlusher blinearFlusher = null; - private static boolean regionFormatLoaded = false; - public static boolean isReadOnlyMode() { return LeafConstants.LINEAR_V2_READ_ONLY && regionFormat == EnumRegionFormat.LINEAR_V2; } @Override public void onLoaded() { - globalConfig.addCommentRegionBased(basePath(), """ - Linear is a region format that uses zstd compression instead of zlib. - This format saves about 50% of disk space. - Read Leaf docs before using!""", - """ - Linear 是一种使用 zstd 压缩而非 ZLIB 的区域格式. - 该格式可节省约 50% 的磁盘空间. - 使用前请阅读 Leaf 文档!"""); - - if (regionFormatLoaded) { - globalConfig.getConfigSection(basePath()); - return; - } - regionFormatLoaded = true; - - regionFormatName = globalConfig.getString(basePath() + ".format-name", regionFormatName, - globalConfig.pickStringRegionBased( - "Available region format names: MCA, B_LINEAR, LINEAR_V2", - "可用格式: MCA, B_LINEAR, LINEAR_V2")); - compressionLevel = globalConfig.getInt(basePath() + ".compress-level", compressionLevel); - ioThreadCount = globalConfig.getInt(basePath() + ".io-thread-count", ioThreadCount); - ioFlushDelay = globalConfig.getInt(basePath() + ".io-flush-delay", ioFlushDelay); - linearUseVirtualThread = globalConfig.getBoolean(basePath() + ".linear-use-virtual-thread", linearUseVirtualThread); - regionFormat = EnumRegionFormat.fromString(regionFormatName); if (regionFormat == EnumRegionFormat.UNKNOWN) { - LOGGER.error("Unknown region format type {}! Falling back to MCA format.", regionFormatName); + LeafConfig.LOGGER.error("Unknown region format type {}! Falling back to MCA format.", regionFormatName); regionFormat = EnumRegionFormat.MCA; return; } if (regionFormat == EnumRegionFormat.LINEAR_V2) { checkCompressionLevel(); - LOGGER.warn("Linear v2 region format is unstable and not recommended to use, beware of data loss and take backups."); + LeafConfig.LOGGER.warn("Linear v2 region format is unstable and not recommended to use, beware of data loss and take backups."); if (isReadOnlyMode()) { - LOGGER.error("============================================================"); - LOGGER.error(" LINEAR_V2 READ-ONLY MODE "); - LOGGER.error("============================================================"); - LOGGER.error("Linear v2 read-only mode is enabled."); - LOGGER.error("Any world changes in Linear v2 regions will NOT be saved."); - LOGGER.error("Chunk, entity, player data and POI changes will be discarded."); - LOGGER.error("This mode is intended for inspection, testing, migration, or emergency recovery."); - LOGGER.error("To enable LINEAR_V2 writing, stop the server, take backups,"); - LOGGER.error("then remove the JVM flag: -D{}=true", LeafConstants.LINEAR_V2_READ_ONLY_FLAG); - LOGGER.error("============================================================"); + LeafConfig.LOGGER.error("============================================================"); + LeafConfig.LOGGER.error(" LINEAR_V2 READ-ONLY MODE "); + LeafConfig.LOGGER.error("============================================================"); + LeafConfig.LOGGER.error("Linear v2 read-only mode is enabled."); + LeafConfig.LOGGER.error("Any world changes in Linear v2 regions will NOT be saved."); + LeafConfig.LOGGER.error("Chunk, entity, player data and POI changes will be discarded."); + LeafConfig.LOGGER.error("This mode is intended for inspection, testing, migration, or emergency recovery."); + LeafConfig.LOGGER.error("To enable LINEAR_V2 writing, stop the server, take backups,"); + LeafConfig.LOGGER.error("then remove the JVM flag: -D{}=true", LeafConstants.LINEAR_V2_READ_ONLY_FLAG); + LeafConfig.LOGGER.error("============================================================"); } LinearRegionFile.SAVE_DELAY_MS = ioFlushDelay <= 0 ? 100 : ioFlushDelay; LinearRegionFile.SAVE_THREAD_MAX_COUNT = ioThreadCount; @@ -96,8 +87,8 @@ public void onLoaded() { private static void checkCompressionLevel() { if (compressionLevel > 22 || compressionLevel < 1) { - LOGGER.error("Linear or BufferedLinear region compression level should be between 1 and 22, but got {} in config", compressionLevel); - LOGGER.error("Falling back to compression level 1."); + LeafConfig.LOGGER.error("Linear or BufferedLinear region compression level should be between 1 and 22, but got {} in config", compressionLevel); + LeafConfig.LOGGER.error("Falling back to compression level 1."); compressionLevel = 1; } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/RemoveChangeNonEditableSignWarning.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/RemoveChangeNonEditableSignWarning.java index 7d20ba0755..bab0416026 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/RemoveChangeNonEditableSignWarning.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/RemoveChangeNonEditableSignWarning.java @@ -1,22 +1,14 @@ package org.dreeam.leaf.config.modules.misc; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; -public class RemoveChangeNonEditableSignWarning extends ConfigModule { - - public String basePath() { - return ConfigCategory.MISC.basePath(); - } +@ConfigClassInfo(category = ConfigCategory.MISC) +public class RemoveChangeNonEditableSignWarning implements ConfigModule { + @ConfigInfo(name = "remove-change-non-editable-sign-warning", comments = { + "Enable to prevent console spam.", + "移除修改无法编辑的告示牌时输出的警告." + }) public static boolean enabled = false; - - @Override - public void onLoaded() { - enabled = globalConfig.getBoolean(basePath() + ".remove-change-non-editable-sign-warning", enabled, - globalConfig.pickStringRegionBased( - "Enable to prevent console spam.", - "移除修改无法编辑的告示牌时输出的警告." - )); - } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/RemoveSpigotCheckBungee.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/RemoveSpigotCheckBungee.java index 815e6fcd34..612ba03db1 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/RemoveSpigotCheckBungee.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/RemoveSpigotCheckBungee.java @@ -1,22 +1,17 @@ package org.dreeam.leaf.config.modules.misc; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; -public class RemoveSpigotCheckBungee extends ConfigModule { - - public String basePath() { - return ConfigCategory.MISC.basePath() + ".remove-spigot-check-bungee-config"; - } +@ConfigClassInfo(category = ConfigCategory.MISC) +public class RemoveSpigotCheckBungee implements ConfigModule { + @ConfigInfo(name = "remove-spigot-check-bungee-config", comments = { + """ + Enable player enter backend server through proxy + without backend server enabling its bungee mode.""", + """ + 使服务器无需打开 bungee 模式即可让玩家加入后端服务器.""" + }) public static boolean enabled = false; - - @Override - public void onLoaded() { - enabled = globalConfig.getBoolean(basePath(), enabled, globalConfig.pickStringRegionBased(""" - Enable player enter backend server through proxy - without backend server enabling its bungee mode.""", - """ - 使服务器无需打开 bungee 模式即可让玩家加入后端服务器.""")); - } } 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 deleted file mode 100644 index 6eb1dacaca..0000000000 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/SecureSeed.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.dreeam.leaf.config.modules.misc; - -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.LeafConfig; -import org.dreeam.leaf.config.LeafWorldConfig; -import org.dreeam.leaf.config.WorldConfigModule; - -public class SecureSeed extends ConfigModule implements WorldConfigModule { - - /** - * 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() { - } - - @Override - public void loadWorldConfig(LeafWorldConfig config) { - String path = "misc.secure-seed"; - 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.""", """ - 安全种子开启后,所有矿物与结构都将使用 1024 位种子,而非原版的 64 位种子, - 从而无法被破解。"""); - } - config.secureSeedEnabled = config.getBoolean(path + ".enabled", false); - } - - @Override - public void onLoaded() { - // Secure Seed is registered by loadWorldConfig. - } - - /** 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/misc/SentryDSN.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/SentryDSN.java index 6e1eb51f22..23b00c941b 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/SentryDSN.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/SentryDSN.java @@ -1,40 +1,41 @@ package org.dreeam.leaf.config.modules.misc; import org.apache.logging.log4j.Level; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; -public class SentryDSN extends ConfigModule { +@ConfigClassInfo(category = ConfigCategory.MISC, name = "sentry") +public class SentryDSN implements ConfigModule { - public String basePath() { - return ConfigCategory.MISC.basePath() + ".sentry"; - } + public static @DoNotLoad String sentryDsnConfigPath = "misc.sentry.dsn"; - public static String sentryDsnConfigPath; + @ConfigInfo(name = "dsn", comments = { + """ + Sentry DSN for improved error logging, leave blank to disable, + Obtain from https://sentry.io/""", + """ + Sentry DSN (出现严重错误时将发送至配置的Sentry DSN地址) (留空关闭)""" + }) public static String sentryDsn = ""; + + @ConfigInfo(name = "log-level", comments = { + "Logs with a level higher than or equal to this level will be recorded.", + "大于等于该等级的日志会被记录." + }) public static String logLevel = "WARN"; + + @ConfigInfo(name = "only-log-thrown", comments = { + "Only log with a Throwable will be recorded after enabling this.", + "是否仅记录带有 Throwable 的日志." + }) public static boolean onlyLogThrown = true; @Override public void onLoaded() { String sentryEnvironment = System.getenv("SENTRY_DSN"); - String sentryConfig = globalConfig.getString(sentryDsnConfigPath = basePath() + ".dsn", sentryDsn, globalConfig.pickStringRegionBased(""" - Sentry DSN for improved error logging, leave blank to disable, - Obtain from https://sentry.io/""", - """ - Sentry DSN (出现严重错误时将发送至配置的Sentry DSN地址) (留空关闭)""")); - - sentryDsn = sentryEnvironment == null - ? sentryConfig - : sentryEnvironment; - logLevel = globalConfig.getString(basePath() + ".log-level", logLevel, globalConfig.pickStringRegionBased(""" - Logs with a level higher than or equal to this level will be recorded.""", - """ - 大于等于该等级的日志会被记录.""")); - onlyLogThrown = globalConfig.getBoolean(basePath() + ".only-log-thrown", onlyLogThrown, globalConfig.pickStringRegionBased(""" - Only log with a Throwable will be recorded after enabling this.""", - """ - 是否仅记录带有 Throwable 的日志.""")); + if (sentryEnvironment != null) { + sentryDsn = sentryEnvironment; + } if (sentryDsn != null && !sentryDsn.isBlank()) { gg.pufferfish.pufferfish.sentry.SentryManager.init(Level.getLevel(logLevel)); diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/ServerBrand.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/ServerBrand.java index 84dea545d5..013ffa8d7a 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/ServerBrand.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/ServerBrand.java @@ -1,20 +1,14 @@ package org.dreeam.leaf.config.modules.misc; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; -public class ServerBrand extends ConfigModule { - - public String basePath() { - return ConfigCategory.MISC.basePath() + ".rebrand"; - } +@ConfigClassInfo(category = ConfigCategory.MISC, name = "rebrand") +public class ServerBrand implements ConfigModule { + @ConfigInfo(name = "server-mod-name") public static String serverModName = io.papermc.paper.ServerBuildInfo.buildInfo().brandName(); - public static String serverGUIName = io.papermc.paper.ServerBuildInfo.buildInfo().brandName() + " Console"; - @Override - public void onLoaded() { - serverModName = globalConfig.getString(basePath() + ".server-mod-name", serverModName); - serverGUIName = globalConfig.getString(basePath() + ".server-gui-name", serverGUIName); - } + @ConfigInfo(name = "server-gui-name") + public static String serverGUIName = io.papermc.paper.ServerBuildInfo.buildInfo().brandName() + " Console"; } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/UnknownCommandMessage.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/UnknownCommandMessage.java index b6c75afe46..d032883159 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/UnknownCommandMessage.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/UnknownCommandMessage.java @@ -1,27 +1,22 @@ package org.dreeam.leaf.config.modules.misc; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; -public class UnknownCommandMessage extends ConfigModule { - - public String basePath() { - return ConfigCategory.MISC.basePath() + ".message"; - } +@ConfigClassInfo(category = ConfigCategory.MISC, name = "message") +public class UnknownCommandMessage implements ConfigModule { + @ConfigInfo(name = "unknown-command", comments = { + """ + Unknown command message, using MiniMessage format, set to "default" to use vanilla message, + placeholder: + , show message of the command exception. + , shows detail of the command exception.""", + """ + 发送未知命令时的消息, 使用 MiniMessage 格式, 设置为 "default" 使用原版消息. + 变量: + , 显示命令错误所附提示消息. + , 显示命令错误详细信息.""" + }) public static String unknownCommandMessage = "default"; - - @Override - public void onLoaded() { - unknownCommandMessage = globalConfig.getString(basePath() + ".unknown-command", unknownCommandMessage, globalConfig.pickStringRegionBased(""" - Unknown command message, using MiniMessage format, set to "default" to use vanilla message, - placeholder: - , show message of the command exception. - , shows detail of the command exception.""", - """ - 发送未知命令时的消息, 使用 MiniMessage 格式, 设置为 "default" 使用原版消息. - 变量: - , 显示命令错误所附提示消息. - , 显示命令错误详细信息.""")); - } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/VanillaUsernameCheck.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/VanillaUsernameCheck.java index 8bdd2ed330..a6f79a8936 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/VanillaUsernameCheck.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/VanillaUsernameCheck.java @@ -1,74 +1,83 @@ 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.annotations.Experimental; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; import java.util.regex.Pattern; -public class VanillaUsernameCheck extends ConfigModule { +@ConfigClassInfo(category = ConfigCategory.MISC, name = "vanilla-username-check") +public class VanillaUsernameCheck implements ConfigModule { - public String basePath() { - return ConfigCategory.MISC.basePath() + ".vanilla-username-check"; - } + @ConfigInfo(name = "remove-all-check", comments = { + """ + Remove Vanilla username check, + allowing all characters as username. + WARNING: UNSAFE, USE AT YOUR OWN RISK!""", + """ + 移除原版的用户名验证, + 让所有字符均可作为玩家名. + 警告: 完全移除验证非常不安全, 使用风险自负!""" + }) + public static @Deprecated boolean removeAllCheck = false; - @Deprecated - public static boolean removeAllCheck = false; + @ConfigInfo(name = "enforce-skull-validation", comments = { + """ + Enforce skull validation, + preventing skulls with invalid names from disconnecting the client.""", + """ + 强制启用头颅验证, + 避免所有者带有特殊字符的头颅导致客户端掉线.""" + }) public static boolean enforceSkullValidation = true; - @Experimental - public static boolean allowOldPlayersJoin = false; + + @ConfigInfo(name = "allow-old-players-join", comments = { + """ + Allow old players to join the server after the username regex is changed, + even if their names don't meet the new requirements.""", + """ + 允许老玩家加入修改用户名验证正则后的服务器, + 即使他们的用户名不满足修改后的正则.""" + }) + public static @Experimental boolean allowOldPlayersJoin = false; + + @ConfigInfo(name = "use-username-regex", comments = { + """ + Use username regex to validate usernames, + allowing only characters specified in the regex.""", + """ + 使用用户名正则来验证用户名, + 只允许正则指定的字符.""" + }) public static boolean useUsernameRegex = false; - private static final String defaultRegexString = "^[a-zA-Z0-9_.]*$"; - public static Pattern usernameRegex = Pattern.compile(defaultRegexString); + + @ConfigInfo(name = "username-regex", comments = { + """ + Username regex, + specifying the characters allowed in usernames. + Default: ^[a-zA-Z0-9_.]*$""", + """ + 用户名正则, + 指定允许在用户名中使用的字符. + 默认: ^[a-zA-Z0-9_.]*$""" + }) + private static String usernameRegexString = "^[a-zA-Z0-9_.]*$"; + + public static @DoNotLoad Pattern usernameRegex; + public static boolean shouldSkipNonPlayerNameCheck() { // helper return removeAllCheck || useUsernameRegex; } @Override public void onLoaded() { - removeAllCheck = globalConfig.getBoolean(basePath() + ".remove-all-check", removeAllCheck, globalConfig.pickStringRegionBased(""" - Remove Vanilla username check, - allowing all characters as username. - WARNING: UNSAFE, USE AT YOUR OWN RISK!""", - """ - 移除原版的用户名验证, - 让所有字符均可作为玩家名. - 警告: 完全移除验证非常不安全, 使用风险自负!""")); - enforceSkullValidation = globalConfig.getBoolean(basePath() + ".enforce-skull-validation", enforceSkullValidation, globalConfig.pickStringRegionBased(""" - Enforce skull validation, - preventing skulls with invalid names from disconnecting the client.""", - """ - 强制启用头颅验证, - 避免所有者带有特殊字符的头颅导致客户端掉线.""")); - allowOldPlayersJoin = globalConfig.getBoolean(basePath() + ".allow-old-players-join", allowOldPlayersJoin, globalConfig.pickStringRegionBased(""" - Allow old players to join the server after the username regex is changed, - even if their names don't meet the new requirements.""", - """ - 允许老玩家加入修改用户名验证正则后的服务器, - 即使他们的用户名不满足修改后的正则.""")); - useUsernameRegex = globalConfig.getBoolean(basePath() + ".use-username-regex", useUsernameRegex, globalConfig.pickStringRegionBased(""" - Use username regex to validate usernames, - allowing only characters specified in the regex.""", - """ - 使用用户名正则来验证用户名, - 只允许正则指定的字符.""")); - String regexString = globalConfig.getString(basePath() + ".username-regex", defaultRegexString, globalConfig.pickStringRegionBased( - """ - Username regex, - specifying the characters allowed in usernames. - Default: %s""".formatted(defaultRegexString), - """ - 用户名正则, - 指定允许在用户名中使用的字符. - 默认: %s""".formatted(defaultRegexString))); - if (!regexString.isBlank()) { + if (!usernameRegexString.isBlank()) { try { - usernameRegex = Pattern.compile(regexString); + usernameRegex = Pattern.compile(usernameRegexString); } catch (Exception e) { - LeafConfig.LOGGER.error("Invalid username regex {} found, falling back to default.", regexString, e); + LeafConfig.LOGGER.error("Invalid username regex {} found, falling back to default.", usernameRegexString, e); } } + if (useUsernameRegex && removeAllCheck) { LeafConfig.LOGGER.warn("Found conflicting configuration, remove-all-check and use-username-regex cannot be enabled at same time, ignoring remove-all-check..."); removeAllCheck = false; 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 deleted file mode 100644 index 0102ed8eea..0000000000 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/WorldConfigExample.java +++ /dev/null @@ -1,21 +0,0 @@ -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 index 2111907bb6..7f8590d47f 100644 --- 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 @@ -1,9 +1,7 @@ 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; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; @ConfigClassInfo(category = ConfigCategory.MISC, name = "chat-logging") public final class Chat implements ConfigModule { 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 index a2ce6403d8..565f0e7488 100644 --- 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 @@ -1,9 +1,7 @@ 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; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; @ConfigClassInfo(category = ConfigCategory.MISC, name = "last-tick-time-in-tps-command") public final class LastTickTimeInTpsCommand implements ConfigModule { 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 index ff8d6d9bba..aa404fad75 100644 --- 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 @@ -1,9 +1,7 @@ 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; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; @ConfigClassInfo(category = ConfigCategory.MISC, name = "log-to-console") public final class LogToConsole implements ConfigModule { 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 index a0157763ee..06619d6999 100644 --- 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 @@ -1,10 +1,8 @@ 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; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; @ConfigClassInfo(category = ConfigCategory.MISC, name = "plugin-library-loader") public final class PluginLibraryLoader implements ConfigModule { diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/world/SecureSeed.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/world/SecureSeed.java new file mode 100644 index 0000000000..34a0c3c3e9 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/world/SecureSeed.java @@ -0,0 +1,21 @@ +package org.dreeam.leaf.config.modules.misc.world; + +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; + +@ConfigClassInfo(category = ConfigCategory.MISC, name = "secure-seed", comments = { + """ + 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位的种子进行生成, 无法被破解.""" +}) +public class SecureSeed implements WorldConfigModule { + + @ConfigInfo(name = "enabled") + public boolean enabled = false; + + public static boolean isEnabled() { + return LeafConfig.worldDefaultsConfig().secureSeed.enabled; + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/AlternativeJoin.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/AlternativeJoin.java index 3dd7123fbd..3d8187e5ca 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/AlternativeJoin.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/AlternativeJoin.java @@ -1,20 +1,14 @@ package org.dreeam.leaf.config.modules.network; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; -public class AlternativeJoin extends ConfigModule { - - public String basePath() { - return ConfigCategory.NETWORK.basePath(); - } +@ConfigClassInfo(category = ConfigCategory.NETWORK) +public class AlternativeJoin implements ConfigModule { + @ConfigInfo(name = "async-switch-state", comments = { + "Async switch connection state.", + "异步切换连接状态." + }) public static boolean enabled = false; - - @Override - public void onLoaded() { - enabled = globalConfig.getBoolean(basePath() + ".async-switch-state", enabled, globalConfig.pickStringRegionBased( - "Async switch connection state.", - "异步切换连接状态.")); - } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/ChatMessageSignature.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/ChatMessageSignature.java index 79cf9749b5..54fe2d1a43 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/ChatMessageSignature.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/ChatMessageSignature.java @@ -1,24 +1,19 @@ package org.dreeam.leaf.config.modules.network; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; -public class ChatMessageSignature extends ConfigModule { - - public String basePath() { - return ConfigCategory.NETWORK.basePath(); - } +@ConfigClassInfo(category = ConfigCategory.NETWORK) +public class ChatMessageSignature implements ConfigModule { + @ConfigInfo(name = "chat-message-signature", comments = { + """ + Whether or not enable chat message signature, + disable will prevent players to report chat messages. + And also disables the popup when joining a server without + 'secure chat', such as offline-mode servers.""", + """ + 是否启用聊天签名, 禁用后玩家无法进行聊天举报.""" + }) public static boolean enabled = true; - - @Override - public void onLoaded() { - enabled = globalConfig.getBoolean(basePath() + ".chat-message-signature", enabled, globalConfig.pickStringRegionBased(""" - Whether or not enable chat message signature, - disable will prevent players to report chat messages. - And also disables the popup when joining a server without - 'secure chat', such as offline-mode servers.""", - """ - 是否启用聊天签名, 禁用后玩家无法进行聊天举报.""")); - } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/ChatOrderVerification.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/ChatOrderVerification.java new file mode 100644 index 0000000000..1168ee4e4c --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/ChatOrderVerification.java @@ -0,0 +1,11 @@ +package org.dreeam.leaf.config.modules.network; + +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; + +@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/Keepalive.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/Keepalive.java new file mode 100644 index 0000000000..5215a8a1e7 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/Keepalive.java @@ -0,0 +1,11 @@ +package org.dreeam.leaf.config.modules.network; + +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; + +@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/OptimizeNonFlushPacketSending.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/OptimizeNonFlushPacketSending.java index ff375bc7bc..021623b2d1 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 @@ -1,37 +1,28 @@ package org.dreeam.leaf.config.modules.network; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; -public class OptimizeNonFlushPacketSending extends ConfigModule { - - public String basePath() { - return ConfigCategory.NETWORK.basePath(); - } +@ConfigClassInfo(category = ConfigCategory.NETWORK) +public class OptimizeNonFlushPacketSending implements ConfigModule { + @ConfigInfo(name = "OptimizeNonFlushPacketSending", comments = { + """ + WARNING: This option is NOT compatible with ProtocolLib and may cause + issues with other plugins that modify packet handling. + + Optimizes non-flush packet sending by using Netty's lazyExecute method to avoid + expensive thread wakeup calls when scheduling packet operations. + + Requires server restart to take effect.""", + """ + 警告: 此选项与 ProtocolLib 不兼容, 并可能导致与其他修改数据包 + 处理的插件出现问题. + + 通过使用 Netty 的 lazyExecute 方法来优化非刷新数据包的发送, + 避免在调度数据包操作时进行昂贵的线程唤醒调用. + + 需要重启服务器才能生效.""" + }) public static boolean enabled = false; - - @Override - public void onLoaded() { - enabled = globalConfig.getBoolean(basePath() + ".OptimizeNonFlushPacketSending", enabled, globalConfig.pickStringRegionBased(""" - WARNING: This option is NOT compatible with ProtocolLib and may cause - issues with other plugins that modify packet handling. - - Optimizes non-flush packet sending by using Netty's lazyExecute method to avoid - expensive thread wakeup calls when scheduling packet operations. - - Requires server restart to take effect.""", - """ - 警告: 此选项与 ProtocolLib 不兼容, 并可能导致与其他修改数据包 - 处理的插件出现问题. - - 通过使用 Netty 的 lazyExecute 方法来优化非刷新数据包的发送, - 避免在调度数据包操作时进行昂贵的线程唤醒调用. - - 需要重启服务器才能生效.""")); - } - - private void migrateConfigPath() { - globalConfig.migratePath(basePath() + ".OptimizeNonFlushPacketSending", basePath() + ".optimizeNonFlushPacketSending"); - } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/PremiumAccountSlowLoginTimeout.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/PremiumAccountSlowLoginTimeout.java new file mode 100644 index 0000000000..993298999d --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/PremiumAccountSlowLoginTimeout.java @@ -0,0 +1,11 @@ +package org.dreeam.leaf.config.modules.network; + +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; + +@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/network/ProtocolSupport.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java index a2b3f96210..d6d1b57a9d 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java @@ -1,57 +1,67 @@ package org.dreeam.leaf.config.modules.network; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; import org.dreeam.leaf.protocol.DoABarrelRollPackets; import org.dreeam.leaf.protocol.DoABarrelRollProtocol; import java.util.concurrent.ThreadLocalRandom; -public class ProtocolSupport extends ConfigModule { - - public String basePath() { - return ConfigCategory.NETWORK.basePath() + ".protocol-support"; - } +@ConfigClassInfo(category = ConfigCategory.NETWORK, name = "protocol-support") +public class ProtocolSupport implements ConfigModule { + @ConfigInfo(name = "strict-mode") public static boolean strictMode = false; + + @ConfigInfo(name = "jade-protocol") public static boolean jadeProtocol = false; + + @ConfigInfo(name = "appleskin-protocol") public static boolean appleskinProtocol = false; + + @ConfigInfo(name = "appleskin-protocol-sync-tick-interval") public static int appleskinSyncTickInterval = 20; + + @ConfigInfo(name = "asteorbar-protocol") public static boolean asteorBarProtocol = false; + + @ConfigInfo(name = "chatimage-protocol") public static boolean chatImageProtocol = false; + + @ConfigInfo(name = "xaero-map-protocol") public static boolean xaeroMapProtocol = false; + + @ConfigInfo(name = "xaero-map-server-id") public static int xaeroMapServerID = ThreadLocalRandom.current().nextInt(); // Leaf - Faster Random + + @ConfigInfo(name = "syncmatica-protocol") public static boolean syncmaticaProtocol = false; + + @ConfigInfo(name = "syncmatica-quota") public static boolean syncmaticaQuota = false; + + @ConfigInfo(name = "syncmatica-quota-limit") public static int syncmaticaQuotaLimit = 40000000; + @ConfigInfo(name = "do-a-barrel-roll-protocol") public static boolean doABarrelRollProtocol = false; + + @ConfigInfo(name = "do-a-barrel-roll-allow-thrusting") public static boolean doABarrelRollAllowThrusting = false; + + @ConfigInfo(name = "do-a-barrel-roll-force-enabled") public static boolean doABarrelRollForceEnabled = false; + + @ConfigInfo(name = "do-a-barrel-roll-force-installed") public static boolean doABarrelRollForceInstalled = false; + + @ConfigInfo(name = "do-a-barrel-roll-installed-timeout") public static int doABarrelRollInstalledTimeout = 40; @Override public void onLoaded() { - strictMode = globalConfig.getBoolean(basePath() + ".strict-mode", strictMode); - jadeProtocol = globalConfig.getBoolean(basePath() + ".jade-protocol", jadeProtocol); - appleskinProtocol = globalConfig.getBoolean(basePath() + ".appleskin-protocol", appleskinProtocol); - appleskinSyncTickInterval = globalConfig.getInt(basePath() + ".appleskin-protocol-sync-tick-interval", appleskinSyncTickInterval); - asteorBarProtocol = globalConfig.getBoolean(basePath() + ".asteorbar-protocol", asteorBarProtocol); - chatImageProtocol = globalConfig.getBoolean(basePath() + ".chatimage-protocol", chatImageProtocol); - xaeroMapProtocol = globalConfig.getBoolean(basePath() + ".xaero-map-protocol", xaeroMapProtocol); - xaeroMapServerID = globalConfig.getInt(basePath() + ".xaero-map-server-id", xaeroMapServerID); - syncmaticaProtocol = globalConfig.getBoolean(basePath() + ".syncmatica-protocol", syncmaticaProtocol); - syncmaticaQuota = globalConfig.getBoolean(basePath() + ".syncmatica-quota", syncmaticaQuota); - syncmaticaQuotaLimit = globalConfig.getInt(basePath() + ".syncmatica-quota-limit", syncmaticaQuotaLimit); - org.leavesmc.leaves.protocol.syncmatica.SyncmaticaProtocol.init(syncmaticaProtocol); - doABarrelRollProtocol = globalConfig.getBoolean(basePath() + ".do-a-barrel-roll-protocol", doABarrelRollProtocol); - doABarrelRollAllowThrusting = globalConfig.getBoolean(basePath() + ".do-a-barrel-roll-allow-thrusting", doABarrelRollAllowThrusting); - doABarrelRollForceEnabled = globalConfig.getBoolean(basePath() + ".do-a-barrel-roll-force-enabled", doABarrelRollForceEnabled); - doABarrelRollForceInstalled = globalConfig.getBoolean(basePath() + ".do-a-barrel-roll-force-installed", doABarrelRollForceInstalled); - doABarrelRollInstalledTimeout = globalConfig.getInt(basePath() + ".do-a-barrel-roll-installed-timeout", 0); if (doABarrelRollInstalledTimeout <= 0) { doABarrelRollInstalledTimeout = 40; } 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 deleted file mode 100644 index 87abaaeb80..0000000000 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/global/ChatOrderVerification.java +++ /dev/null @@ -1,13 +0,0 @@ -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 deleted file mode 100644 index a067a199b2..0000000000 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/global/Keepalive.java +++ /dev/null @@ -1,13 +0,0 @@ -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 deleted file mode 100644 index 4a4748b6b5..0000000000 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/global/PremiumAccountSlowLoginTimeout.java +++ /dev/null @@ -1,13 +0,0 @@ -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/CheckSurvivalBeforeGrowth.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/CheckSurvivalBeforeGrowth.java index d7ff1da795..0be418f678 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/CheckSurvivalBeforeGrowth.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/CheckSurvivalBeforeGrowth.java @@ -1,22 +1,14 @@ package org.dreeam.leaf.config.modules.opt; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; -public class CheckSurvivalBeforeGrowth extends ConfigModule { - - public String basePath() { - return ConfigCategory.PERF.basePath() + ".check-survival-before-growth"; - } +@ConfigClassInfo(category = ConfigCategory.PERF, name = "check-survival-before-growth") +public class CheckSurvivalBeforeGrowth implements ConfigModule { + @ConfigInfo(name = "cactus-check-survival", comments = { + "Check if a cactus can survive before growing.", + "在仙人掌生长前检查其是否能够存活。" + }) public static boolean cactusCheckSurvivalBeforeGrowth = false; - - @Override - public void onLoaded() { - cactusCheckSurvivalBeforeGrowth = globalConfig.getBoolean(basePath() + ".cactus-check-survival", cactusCheckSurvivalBeforeGrowth, - globalConfig.pickStringRegionBased(""" - Check if a cactus can survive before growing.""", - """ - 在仙人掌生长前检查其是否能够存活。""")); - } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/DespawnTime.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/DespawnTime.java index e77c414529..78a4ca3bce 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/DespawnTime.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/DespawnTime.java @@ -1,26 +1,19 @@ package org.dreeam.leaf.config.modules.opt; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; -import org.dreeam.leaf.config.annotations.Experimental; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; -public class DespawnTime extends ConfigModule { - - public String basePath() { - return ConfigCategory.PERF.basePath() + ".despawn-time"; - } +@ConfigClassInfo(category = ConfigCategory.PERF, name = "despawn-time") +public class DespawnTime implements ConfigModule { @Experimental + @ConfigInfo(name = "proactive-weak-loading-despawn", comments = { + """ + Proactive despawn check for weak-loaded entities. + This is an experimental feature.""", + """ + 启用主动弱加载实体消失检查, + 这是一个实验性功能。""" + }) public static boolean proactiveWeakLoading = false; - - @Override - public void onLoaded() { - proactiveWeakLoading = globalConfig.getBoolean(basePath() + ".proactive-weak-loading-despawn", proactiveWeakLoading, - globalConfig.pickStringRegionBased(""" - Proactive despawn check for weak-loaded entities. - This is an experimental feature.""", - """ - 启用主动弱加载实体消失检查, - 这是一个实验性功能。""")); - } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/DontSaveEntity.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/DontSaveEntity.java index f4d14107ef..a0620d0aa4 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/DontSaveEntity.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/DontSaveEntity.java @@ -1,27 +1,22 @@ package org.dreeam.leaf.config.modules.opt; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; -public class DontSaveEntity extends ConfigModule { - - public String basePath() { - return ConfigCategory.PERF.basePath() + ".dont-save-entity"; - } +@ConfigClassInfo(category = ConfigCategory.PERF, name = "dont-save-entity") +public class DontSaveEntity implements ConfigModule { + @ConfigInfo(name = "dont-save-primed-tnt", comments = { + """ + Disable save primed tnt on chunk unloads. + Useful for redstone/technical servers, can prevent machines from being exploded by TNT, + when player disconnected caused by Internet issue.""", + """ + 区块卸载时不保存掉落的方块和激活的 TNT, + 可以避免在玩家掉线时机器被炸毁.""" + }) public static boolean dontSavePrimedTNT = false; - public static boolean dontSaveFallingBlock = false; - @Override - public void onLoaded() { - dontSavePrimedTNT = globalConfig.getBoolean(basePath() + ".dont-save-primed-tnt", dontSavePrimedTNT, - globalConfig.pickStringRegionBased(""" - Disable save primed tnt on chunk unloads. - Useful for redstone/technical servers, can prevent machines from being exploded by TNT, - when player disconnected caused by Internet issue.""", - """ - 区块卸载时不保存掉落的方块和激活的 TNT, - 可以避免在玩家掉线时机器被炸毁.""")); - dontSaveFallingBlock = globalConfig.getBoolean(basePath() + ".dont-save-falling-block", dontSaveFallingBlock); - } + @ConfigInfo(name = "dont-save-falling-block") + public static boolean dontSaveFallingBlock = false; } 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 9e0ad6a4ac..eddac6533b 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 @@ -3,27 +3,67 @@ import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.Identifier; import net.minecraft.world.entity.EntityType; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; -import org.dreeam.leaf.config.LeafConfig; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Locale; -public class DynamicActivationofBrain extends ConfigModule { - - public String basePath() { - return ConfigCategory.PERF.basePath() + ".dab"; - } +@ConfigClassInfo(category = ConfigCategory.PERF, name = "dab", comments = { + """ + Optimizes entity brains when + they're far away from the player""", + """ + 根据距离动态优化生物 AI""" +}) +public class DynamicActivationofBrain implements ConfigModule { + @ConfigInfo(name = "enabled") public static boolean enabled = false; + + @ConfigInfo(name = "dont-enable-if-in-water", comments = { + """ + After enabling this, non-aquatic entities in the water will not be affected by DAB. + This could fix entities suffocate in the water.""", + """ + 启用此项后, 在水中的非水生生物将不会被 DAB 影响. + 可以避免距离玩家较远的生物在水里淹死.""" + }) + public static boolean dontEnableIfInWater = false; + + @ConfigInfo(name = "start-distance", comments = { + """ + This value determines how far away an entity has to be + from the player to start being effected by DEAR.""", + """ + 生物距离玩家多少格 DAB 开始生效""" + }) public static double startDistance = 12.0; - public static double startDistanceSquared; + + @ConfigInfo(name = "max-tick-freq", comments = { + """ + This value defines how often in ticks, the furthest entity + will get their pathfinders and behaviors ticked. 20 = 1s""", + """ + 最远处的实体每隔多少刻tick一次""" + }) public static int maximumActivationPrio = 20; + + @ConfigInfo(name = "activation-dist-mod", comments = { + """ + This value defines how much distance modifies an entity's + tick frequency. freq = (distanceToPlayer^2) / (2^value)", + If you want further away entities to tick less often, use 7. + If you want further away entities to tick more often, try 9.""" + }) public static double activationDistanceMod = 8.0; - public static boolean dontEnableIfInWater = false; + + @ConfigInfo(name = "blacklisted-entities", comments = { + "A list of entities to ignore for activation", + "不会被 DAB 影响的实体列表" + }) public static List blackedEntities = new ArrayList<>(Arrays.asList( "villager", "axolotl", @@ -32,40 +72,10 @@ public String basePath() { "goat" )); + public static @DoNotLoad double startDistanceSquared; + @Override public void onLoaded() { - globalConfig.addCommentRegionBased(basePath(), """ - Optimizes entity brains when - they're far away from the player""", - """ - 根据距离动态优化生物 AI"""); - - enabled = globalConfig.getBoolean(basePath() + ".enabled", enabled); - dontEnableIfInWater = globalConfig.getBoolean(basePath() + ".dont-enable-if-in-water", dontEnableIfInWater, globalConfig.pickStringRegionBased(""" - After enabling this, non-aquatic entities in the water will not be affected by DAB. - This could fix entities suffocate in the water.""", - """ - 启用此项后, 在水中的非水生生物将不会被 DAB 影响. - 可以避免距离玩家较远的生物在水里淹死.""")); - startDistance = globalConfig.getDouble(basePath() + ".start-distance", startDistance, globalConfig.pickStringRegionBased(""" - This value determines how far away an entity has to be - from the player to start being effected by DEAR.""", - """ - 生物距离玩家多少格 DAB 开始生效""")); - maximumActivationPrio = globalConfig.getInt(basePath() + ".max-tick-freq", maximumActivationPrio, globalConfig.pickStringRegionBased(""" - This value defines how often in ticks, the furthest entity - will get their pathfinders and behaviors ticked. 20 = 1s""", - """ - 最远处的实体每隔多少刻tick一次""")); - activationDistanceMod = globalConfig.getDouble(basePath() + ".activation-dist-mod", activationDistanceMod, """ - This value defines how much distance modifies an entity's - tick frequency. freq = (distanceToPlayer^2) / (2^value)", - If you want further away entities to tick less often, use 7. - If you want further away entities to tick more often, try 9."""); - blackedEntities = globalConfig.getList(basePath() + ".blacklisted-entities", blackedEntities, - globalConfig.pickStringRegionBased("A list of entities to ignore for activation", - "不会被 DAB 影响的实体列表")); - startDistanceSquared = startDistance * startDistance; } @@ -85,7 +95,7 @@ public void onRegistriesLoaded() { EntityType.byString(typeId).ifPresentOrElse(entityType -> entityType.dabEnabled = false, - () -> LeafConfig.LOGGER.warn("Skip unknown entity {}, in {}", name, basePath() + ".blacklisted-entities") + () -> LeafConfig.LOGGER.warn("Skip unknown entity {}, in performance.dab.blacklisted-entities", name) ); } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/EnableCachedMTBEntityTypeConvert.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/EnableCachedMTBEntityTypeConvert.java index bf2f20be19..b7a4b1ecaf 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/EnableCachedMTBEntityTypeConvert.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/EnableCachedMTBEntityTypeConvert.java @@ -1,21 +1,14 @@ package org.dreeam.leaf.config.modules.opt; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; -public class EnableCachedMTBEntityTypeConvert extends ConfigModule { - - public String basePath() { - return ConfigCategory.PERF.basePath(); - } +@ConfigClassInfo(category = ConfigCategory.PERF) +public class EnableCachedMTBEntityTypeConvert implements ConfigModule { + @ConfigInfo(name = "enable-cached-minecraft-to-bukkit-entitytype-convert", comments = { + "Whether to cache expensive CraftEntityType#minecraftToBukkit call.", + "是否缓存Minecraft到Bukkit的实体类型转换." + }) public static boolean enabled = true; - - @Override - public void onLoaded() { - enabled = globalConfig.getBoolean(basePath() + ".enable-cached-minecraft-to-bukkit-entitytype-convert", enabled, globalConfig.pickStringRegionBased(""" - Whether to cache expensive CraftEntityType#minecraftToBukkit call.""", - """ - 是否缓存Minecraft到Bukkit的实体类型转换.""")); - } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/EntityGoal.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/EntityGoal.java index 7775a10da3..bf13f8ab86 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/EntityGoal.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/EntityGoal.java @@ -1,28 +1,39 @@ package org.dreeam.leaf.config.modules.opt; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; -public class EntityGoal extends ConfigModule { +@ConfigClassInfo(category = ConfigCategory.PERF, name = "entity-goal") +public class EntityGoal implements ConfigModule { - public String basePath() { - return ConfigCategory.PERF.basePath() + ".entity-goal"; - } + @ConfigInfo(name = "start-tick-chance.nearest-attackable-target") + private static int configuredChanceTarget = -1; + + @ConfigInfo(name = "start-tick-chance.follow-parent") + private static int configuredChanceFollowParent = -1; + + @ConfigInfo(name = "start-tick-chance.avoid-entity") + private static int configuredChanceAvoidEntity = -1; + + @ConfigInfo(name = "start-tick-chance.temptation") + private static int configuredChanceTempt = -1; + + @ConfigInfo(name = "start-tick-chance.enderman-look-for-player") + private static int configuredChanceEndermanLookForPlayer = -1; - public static int chanceTarget = -1; // only all <= 10 - public static int chanceFollowParent = -1; - public static int chanceAvoidEntity = -1; - public static int chanceTempt = -1; - public static int chanceEndermanLookForPlayer = -1; + public static @DoNotLoad int chanceTarget = -1; // only all <= 10 + public static @DoNotLoad int chanceFollowParent = -1; + public static @DoNotLoad int chanceAvoidEntity = -1; + public static @DoNotLoad int chanceTempt = -1; + public static @DoNotLoad int chanceEndermanLookForPlayer = -1; @Override public void onLoaded() { - String path = basePath() + ".start-tick-chance"; - chanceTarget = globalConfig.getInt(path + ".nearest-attackable-target", -1); - chanceFollowParent = globalConfig.getInt(path + ".follow-parent", -1); - chanceAvoidEntity = globalConfig.getInt(path + ".avoid-entity", -1); - chanceTempt = globalConfig.getInt(path + ".temptation", -1); - chanceEndermanLookForPlayer = globalConfig.getInt(path + ".enderman-look-for-player", -1); + chanceTarget = configuredChanceTarget; + chanceFollowParent = configuredChanceFollowParent; + chanceAvoidEntity = configuredChanceAvoidEntity; + chanceTempt = configuredChanceTempt; + chanceEndermanLookForPlayer = configuredChanceEndermanLookForPlayer; // expect nearest_attackable_target if (chanceFollowParent < 1) { diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/FasterStructureGenFutureSequencing.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/FasterStructureGenFutureSequencing.java index 2b855c4463..718fa7be4e 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/FasterStructureGenFutureSequencing.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/FasterStructureGenFutureSequencing.java @@ -1,21 +1,14 @@ package org.dreeam.leaf.config.modules.opt; -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; -public class FasterStructureGenFutureSequencing extends ConfigModule { - - public String basePath() { - return ConfigCategory.PERF.basePath(); - } +@ConfigClassInfo(category = ConfigCategory.PERF) +public class FasterStructureGenFutureSequencing implements ConfigModule { + @ConfigInfo(name = "faster-structure-gen-future-sequencing", comments = { + "May cause the inconsistent order of future compose tasks.", + "更快的结构生成任务分段." + }) public static boolean enabled = true; - - @Override - public void onLoaded() { - enabled = globalConfig.getBoolean(basePath() + ".faster-structure-gen-future-sequencing", enabled, - globalConfig.pickStringRegionBased( - "May cause the inconsistent order of future compose tasks.", - "更快的结构生成任务分段.")); - } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/util/ConfigPaths.java b/leaf-server/src/main/java/org/dreeam/leaf/config/util/ConfigPaths.java index 5e190fd0ad..7109003c01 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/util/ConfigPaths.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/util/ConfigPaths.java @@ -15,7 +15,9 @@ public static String modulePath(Class moduleClass) { + " is missing @ConfigClassInfo"); } - return info.category().basePath() + '.' + info.name(); + return info.name().isEmpty() + ? info.category().basePath() + : info.category().basePath() + '.' + info.name(); } public static String fieldPath(Class moduleClass, Field field) { 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 572c0f08dc..e19f2c103a 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 (!world.leafConfig().secureSeedEnabled) return; + if (!world.leafConfig().secureSeed.enabled) return; long[] seed = world.getServer().getWorldGenSettings().options().featureSeed(); System.arraycopy(seed, 0, worldSeed, 0, WORLD_SEED_LONGS);