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..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,7 +19,7 @@ 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 d22b619eb47cd99b7d3d44bf0fa5c6278bbaaf6a..16a0f7ede6924fee0019c6b054b96a2eaf68677d 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 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..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,7 +57,7 @@ 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 16a0f7ede6924fee0019c6b054b96a2eaf68677d..7952d6836743b6ed88221503c905d5c0dfa56e82 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 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..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,7 +33,7 @@ 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 7952d6836743b6ed88221503c905d5c0dfa56e82..a02889c57217422b6231b9708ed8f7cb423da27c 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 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..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,7 +45,7 @@ index 98f07bacb2a94b368c1969dc0cfb11b72f6cea1a..6755281b1dedf2078e1623918985de77 } diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index bc08326c6b8e0657357f57f9bc47ef3c55f7744f..306ab28189d3272efbdaaf09444accf52f570fee 100644 +index 6638155dd34519592893bf992ad5050f30c39626..d8940a667cf9c8216dd67add38debc86c3e276c6 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 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..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,7 +27,7 @@ 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 d8940a667cf9c8216dd67add38debc86c3e276c6..72b03879dee9613423648dd2763a76a28f375704 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 @@ -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 diff --git a/leaf-server/minecraft-patches/features/0326-Leaf-config-changes.patch b/leaf-server/minecraft-patches/features/0326-Leaf-config-changes.patch new file mode 100644 index 0000000000..6de0b55b4e --- /dev/null +++ b/leaf-server/minecraft-patches/features/0326-Leaf-config-changes.patch @@ -0,0 +1,550 @@ +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/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)), 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 -> 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 1c0b616aa34e93e015267fc2415d00431ad494d7..5c7272f449b569d3cdefc296088808b9b94b37c4 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 118f4df4599768488d5fd86e97b0b024bea00365..c7eed64b8446dca61c134ed18283ceb12d6b52c9 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.BookWriting.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 4bbf4cd3ca3d412b70e3a851c2873240baff13c6..77d0e507458ef2bdc4c863860322910e468efdb3 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..435792ec17f7b1171825af22406a83fe21804452 100644 +--- a/net/minecraft/world/level/Level.java ++++ b/net/minecraft/world/level/Level.java +@@ -179,6 +179,16 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + } + // Gale end - Gale configuration + ++ // Leaf start - per-world configuration ++ 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 +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 ++ 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 +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 ++ 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/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/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/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/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/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/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)); 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..3cd0bbdd67 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigBinder.java @@ -0,0 +1,342 @@ +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 org.dreeam.leaf.config.util.ConfigPaths; +import org.jspecify.annotations.Nullable; + +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 + ) 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 instanceof LeafWorldConfig worldConfig) || worldConfig.isWorldDefaults())) { + config.addComment(basePath, sectionComment); + } + + for (Field field : moduleClass.getDeclaredFields()) { + boolean skipLoad = field.getAnnotation(DoNotLoad.class) != null; + ConfigInfo configInfo = field.getAnnotation(ConfigInfo.class); + + if (skipLoad || configInfo == null) { + continue; + } + + if (global) { + bindGlobal(moduleClass, field, configInfo, config); + } else { + bindWorld(module, worldDefaultModule, moduleClass, field, configInfo, config); + } + } + } + + private static void bindGlobal( + Class moduleClass, + Field field, + ConfigInfo configInfo, + LeafConfigAccessor config + ) throws IllegalAccessException { + 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); + field.set(null, loadedValue); + } + + private static void bindWorld( + Object module, + @Nullable Object defaultsModule, + Class moduleClass, + Field field, + ConfigInfo configInfo, + LeafConfigAccessor config + ) 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)) { + // Use world default if no override path defined + field.set(module, defaultValue); + return; + } + + String comment = config.pickStringRegionBased(configInfo.comments()); + Object loadedValue = readValue(config, path, comment, field, defaultValue, !worldOverridden); + field.set(module, loadedValue); + } + + static void applyWorldDefaults( + Object module, + Object defaultsModule + ) throws IllegalAccessException { + Class moduleClass = module.getClass(); + if (defaultsModule.getClass() != moduleClass) { + throw new IllegalArgumentException("Configuration modules must have the same type"); + } + for (Field field : moduleClass.getDeclaredFields()) { + if (field.getAnnotation(DoNotLoad.class) != null + || field.getAnnotation(ConfigInfo.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(); + 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()); + } + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private static Object readValue( + LeafConfigAccessor config, + String path, + @Nullable String comment, + Field field, + Object defaultValue, + boolean writeDefault + ) { + 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; + 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) { + throw new IllegalArgumentException("Invalid value '" + value + "' for " + path + + "; expected one of " + List.of(type.getEnumConstants()), exception); + } + } + 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; + } + + 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/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 c3ac3532eb..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,94 +1,24 @@ 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<>(); - - protected final LeafGlobalConfig globalConfig; - - public ConfigModule() { - this.globalConfig = LeafConfig.globalConfig(); - } - - public static void initModules() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { - List enabledExperimentalModules = new ArrayList<>(); - List deprecatedModules = 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(); - - 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)); - } +/** + * Marker and lifecycle contract for a server-wide Leaf configuration module. + * + *

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() { } - private static List formatModules(List modules) { - return modules.stream().map(f -> f.getDeclaringClass().getSimpleName() + "." + f.getName()).toList(); - } - - 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(); - } - - public abstract void onLoaded(); - - public void onPostLoaded() { + /** + * Runs after this module's configuration fields have been loaded and core registries are available. + */ + default void onRegistriesLoaded() { } } 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 f4e9cc0086..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 @@ -1,12 +1,20 @@ package org.dreeam.leaf.config; +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.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; +import org.dreeam.leaf.config.util.ConfigFileIO; import org.jspecify.annotations.NullMarked; import org.bukkit.Bukkit; import org.bukkit.World; @@ -15,6 +23,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; @@ -26,6 +36,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; @@ -45,37 +56,63 @@ 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"); - 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"; // 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 final List GLOBAL_MODULES = new ArrayList<>(); + private static final List WORLD_MODULES = new ArrayList<>(); + + 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(); + 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)); @@ -83,7 +120,27 @@ 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 LeafWorldConfig loadWorldConfig(Path worldDirectory, LeafWorldConfig defaults) throws Exception { + LeafWorldConfig config = new LeafWorldConfig( + defaults.configFile, + LeafWorldConfig.Source.WORLD_OVERRIDE + ); + applyWorldDefaults(config, defaults); + + File worldConfigFile = worldDirectory.resolve(WORLD_CONFIG_FILE).toFile(); + if (!worldConfigFile.isFile()) { + config.setConfigFile(defaults.configFile); + return config; + } + applyWorldOverride( + config, + ConfigFileIO.load(worldConfigFile), + defaults + ); + return config; } // Init config @@ -93,7 +150,30 @@ public static void loadConfig() { LOGGER.info("Loading config..."); purgeOutdated(); - loadConfig(true); + 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)); + + // 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, true); + if (GLOBAL_MODULES.isEmpty()) { + discoverGlobalModules(); + } + for (ConfigModule module : GLOBAL_MODULES) { + ConfigBinder.bind(module, null, globalConfig, true); + } + runGlobalModuleCallbacks(false); + + worldDefaultsConfig = loadWorldDefaults(worldDefaultsFile); + worldDefaultsConfig.saveConfig(); LOGGER.info("Successfully loaded config in {}ms.", (System.nanoTime() - begin) / 1_000_000); } catch (Exception e) { @@ -103,18 +183,239 @@ public static void loadConfig() { /* Load Global Config */ - private static void loadConfig(boolean init) throws Exception { - // Create config folder - createDirectory(CONFIG_DIRECTORY); + public static LeafGlobalConfig globalConfig() { + return globalConfig; + } + + public static LeafWorldConfig worldDefaultsConfig() { + return worldDefaultsConfig; + } - globalConfig = new LeafGlobalConfig(init); + /** + * 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 + ); + if (migratedConfig != null) { + return migratedConfig; + } + try { + return loadWorldConfig(worldDirectory, worldDefaultsConfig); + } catch (Exception exception) { + throw new RuntimeException("Could not load Leaf world config for " + worldDirectory, exception); + } + } - // Load config modules - ConfigModule.initModules(); + 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(); + ConfigBinder.registerGlobalDefaults(module); + GLOBAL_MODULES.add(module); + } } - public static LeafGlobalConfig globalConfig() { - return globalConfig; + 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(); + + 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( + 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 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); + } + } + + 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(true); + runAfterBootstrapCallbacks(true); + 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(true); + } catch (Exception callbackException) { + exception.addSuppressed(callbackException); + } + try { + runAfterBootstrapCallbacks(true); + } catch (Exception callbackException) { + exception.addSuppressed(callbackException); + } + throw exception; + } + } + + 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); + } + return config; + } + + public static LeafWorldConfig loadWorldOverride( + ConfigFile configFile, + LeafWorldConfig worldDefaults + ) throws ReflectiveOperationException { + 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 + ) 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); + } + } + + public static void loadAfterBootstrap() { + runAfterBootstrapCallbacks(false); + + try { + globalConfig.saveConfig(); + } catch (Exception exception) { + LOGGER.error("Failed to save config file!", exception); + } + } + + private static void runAfterBootstrapCallbacks(boolean reload) { + for (ConfigModule module : GLOBAL_MODULES) { + if (reload && module.getClass().isAnnotationPresent(HotReloadUnsupported.class)) { + continue; + } + module.onRegistriesLoaded(); + } + } + + 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()); + } + + private static void applyWorldDefaults( + LeafWorldConfig config, + 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); + } } static boolean isChineseLocale() { @@ -216,14 +517,107 @@ 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; + } + + static boolean isConfigVersionBefore(String storedVersion, String version) { + return parseStoredConfigVersion(storedVersion, false).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 static ConfigVersion parseStoredConfigVersion(String version, boolean warnIfInvalid) { + if (version == null) { + return ConfigVersion.initial(); + } + + try { + return ConfigVersion.parse(version); + } catch (IllegalArgumentException exception) { + if (warnIfInvalid) { + LOGGER.warn("Invalid Leaf config version '{}'; treating it as an unversioned configuration.", version); + } + return ConfigVersion.initial(); + } + } + + private record WorldReload( + LeafWorldConfig config, + ConfigFile loadedConfigFile, + ConfigFile previousConfigFile + ) { + } - // First time user - if (preVer == null) { + 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; } } @@ -232,8 +626,7 @@ public static void loadConfigVersion(String preVer, String currVer) { private static List buildSparkExtraConfigs() { List extraConfigs = new ArrayList<>(Arrays.asList( "config/leaf-global.yml", - "config/gale-global.yml", - "config/gale-world-defaults.yml" + "config/leaf-world-defaults.yml" )); String existing = System.getProperty(SPARK_EXTRA_CONFIG_PROPERTY); @@ -246,17 +639,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); + 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; @@ -282,9 +679,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/LeafConfigAccessor.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigAccessor.java new file mode 100644 index 0000000000..9bd387fc26 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfigAccessor.java @@ -0,0 +1,191 @@ +package org.dreeam.leaf.config; + +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; +import java.util.List; +import java.util.Map; + +/** Shared configuration-file utilities for global and world configuration views. */ +abstract class LeafConfigAccessor { + + protected ConfigFile configFile; + + protected LeafConfigAccessor(ConfigFile configFile) { + this.configFile = configFile; + } + + void setConfigFile(ConfigFile configFile) { + this.configFile = configFile; + } + + public void saveConfig() throws Exception { + ConfigFileIO.saveAtomically(configFile); + } + + boolean contains(String path) { + return configFile.contains(path); + } + + 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); + } + + // 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]; + return LeafConfig.isChineseLocale() ? localizedStrings[1] : 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 1984a855f7..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 @@ -1,24 +1,22 @@ 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; +import java.util.Objects; -public class LeafGlobalConfig { +/** The server-wide Leaf configuration. */ +public final class LeafGlobalConfig extends LeafConfigAccessor { - private static ConfigFile configFile; + LeafGlobalConfig(ConfigFile configFile, boolean loadPreviousVersion) { + super(configFile); - public LeafGlobalConfig(boolean init) throws Exception { - configFile = ConfigFile.loadConfig(new File(LeafConfig.CONFIG_DIRECTORY, LeafConfig.GLOBAL_CONFIG_FILE)); + if (loadPreviousVersion) { + LeafConfig.loadPreviousConfigVersion(getString("config-version")); + } - //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,188 +29,14 @@ 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"""))); - // 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(); - } - - // 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 new file mode 100644 index 0000000000..ff1d910acf --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafWorldConfig.java @@ -0,0 +1,56 @@ +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.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; +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 + * optional per-world overlay. + * + *

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, + 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 SecureSeed secureSeed = new SecureSeed(); + + LeafWorldConfig(ConfigFile configFile, Source source) { + super(configFile); + this.source = source; + } + + public boolean isWorldDefaults() { + return this.source == Source.WORLD_DEFAULTS; + } +} 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..377d52c439 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/WorldConfigModule.java @@ -0,0 +1,10 @@ +package org.dreeam.leaf.config; + +/** + * Marker for a world-scoped Leaf configuration module. + * + *

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/annotations/ConfigClassInfo.java b/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/ConfigClassInfo.java new file mode 100644 index 0000000000..c88165cb64 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/ConfigClassInfo.java @@ -0,0 +1,26 @@ +package org.dreeam.leaf.config.annotations; + +import org.dreeam.leaf.config.ConfigCategory; + +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 { + + ConfigCategory category(); + + String name() 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 new file mode 100644 index 0000000000..6d57a9d118 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/ConfigInfo.java @@ -0,0 +1,22 @@ +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(); + + /** + * 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/annotations/DoNotLoad.java b/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/DoNotLoad.java index b6687584b7..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 @@ -1,8 +1,20 @@ 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 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) +@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..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 @@ -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; +/** Prevents an annotated field, or every configuration field in an annotated module, from being reloaded. */ +@Documented @Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.TYPE}) public @interface HotReloadUnsupported { } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/migration/ConfigPathMigration.java b/leaf-server/src/main/java/org/dreeam/leaf/config/migration/ConfigPathMigration.java new file mode 100644 index 0000000000..88da7f20cc --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/migration/ConfigPathMigration.java @@ -0,0 +1,38 @@ +package org.dreeam.leaf.config.migration; + +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/migration/LeafConfigMigration.java b/leaf-server/src/main/java/org/dreeam/leaf/config/migration/LeafConfigMigration.java new file mode 100644 index 0000000000..4e34ea40c4 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/migration/LeafConfigMigration.java @@ -0,0 +1,109 @@ +package org.dreeam.leaf.config.migration; + +import io.github.thatsmusic99.configurationmaster.api.ConfigFile; +import io.github.thatsmusic99.configurationmaster.api.ConfigSection; + +import java.util.Objects; + +/** + * Applies versioned migrations to the loaded Leaf configuration instances before defaults are added. + */ +public final class LeafConfigMigration { + + private LeafConfigMigration() { + } + + public static void migrate(ConfigFile globalConfig, ConfigFile worldDefaultsConfig) throws Exception { + String storedVersion = globalConfig.getString("config-version", null); + MigrationContext context = new MigrationContext(globalConfig, worldDefaultsConfig); + + applyMigrations(storedVersion, context); + } + + 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 ConfigFile worldDefaultsConfig; + + private MigrationContext(ConfigFile globalConfig, ConfigFile worldDefaultsConfig) { + this.globalConfig = globalConfig; + this.worldDefaultsConfig = worldDefaultsConfig; + } + + private void migrate( + ConfigFileType source, + String oldPath, + ConfigFileType target, + String newPath + ) throws Exception { + validateMigration(source, oldPath, target, newPath); + + ConfigFile sourceConfig = config(source); + if (!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); + if (targetConfig.contains(newPath)) { + sourceConfig.set(oldPath, null); + } else { + sourceConfig.moveTo(oldPath, newPath, targetConfig); + } + } + + 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) { + return type == ConfigFileType.GLOBAL ? this.globalConfig : this.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 new file mode 100644 index 0000000000..989011a0dc --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/migration/gale/GaleConfigMigration.java @@ -0,0 +1,276 @@ +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.util.ConfigFileIO; +import org.dreeam.leaf.config.util.ConfigPaths; +import org.jspecify.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.nio.file.Files; +import java.nio.file.Path; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.List; +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 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(); + + public static void migrate(Path directory, ConfigFile leafGlobalConfig, ConfigFile leafWorldDefaultsConfig) { + configDirectory = Objects.requireNonNull(directory, "directory").normalize(); + + Path globalPath = configDirectory.resolve(GLOBAL_FILE); + Path worldDefaultsPath = configDirectory.resolve(WORLD_DEFAULTS_FILE); + + registerMappings(); + Map resolvedGlobalMappings = resolveMappings(globalMappings); + resolvedWorldMappings = resolveMappings(worldMappings); + migrateValues(globalPath, resolvedGlobalMappings, leafGlobalConfig); + migrateValues(worldDefaultsPath, resolvedWorldMappings, leafWorldDefaultsConfig); + } + + private static void registerMappings() { + globalMappings = new ArrayList<>(); + worldMappings = new ArrayList<>(); + + 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) { + 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 finalizeMigration(MinecraftServer server) { + if (configDirectory == null) { + return; + } + + 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()); + 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) { + LOGGER.warn( + "Gale configuration migration has finished. Please manually check the migrated Leaf configuration files for correctness." + ); + } + } + + /** + * 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 + ) { + Path leafPath = leafFile.toPath(); + Path galePath = worldDirectory.resolve(WORLD_OVERRIDE_FILE); + + if (!Files.isRegularFile(galePath)) { + return null; + } + + boolean leafFileCreated = false; + try { + if (leafFile.isFile()) { + LOGGER.warn( + "Could not migrate Gale world config {} because Leaf world override {} already exists.", + galePath, leafPath + ); + return null; + } + + ConfigFile galeConfig = loadSrcConfig(galePath); + 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 = ConfigFileIO.load(leafFile); + applyMappings(galeConfig, resolvedWorldMappings, leafConfig); + LeafWorldConfig migrated = LeafConfig.loadWorldOverride(leafConfig, defaults); + migrated.saveConfig(); + 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.", + galePath, worldDirectory, exception + ); + return null; + } + } + + private static void migrateValues(Path srcPath, Map mappings, ConfigFile leafConfig) { + ConfigFile srcConfig = loadSrcConfig(srcPath); + if (srcConfig == null) { + return; + } + applyMappings(srcConfig, mappings, leafConfig); + } + + 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)); + } + } + } + + private static Map resolveMappings(List mappings) { + Map resolvedMappings = new LinkedHashMap<>(); + for (Mapping mapping : mappings) { + String oldPath = mapping.oldPath(); + 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); + } + + String leafPath = ConfigPaths.fieldPath(mapping.moduleClass(), field); + + resolvedMappings.put(oldPath, leafPath); + } + return resolvedMappings; + } + + private static @Nullable ConfigFile loadSrcConfig(Path srcPath) { + if (!Files.isRegularFile(srcPath)) { + return null; + } + try { + 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, Path backupPath) { + if (!Files.isRegularFile(srcPath)) return false; + try { + 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 record Mapping(String oldPath, Class moduleClass, String fieldName) { + } +} 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..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 @@ -1,66 +1,67 @@ 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 = "default"; + + 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 + 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", 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) { 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 new file mode 100644 index 0000000000..eca965c1f0 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/fixes/world/Fixes.java @@ -0,0 +1,17 @@ +package org.dreeam.leaf.config.modules.fixes.world; + +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; + +@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/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/world/EnderDragonRespawn.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/world/EnderDragonRespawn.java new file mode 100644 index 0000000000..7bb065a31b --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/world/EnderDragonRespawn.java @@ -0,0 +1,11 @@ +package org.dreeam.leaf.config.modules.gameplay.world; + +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 { + + @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..28ab1cfe74 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/world/HideFlamesOnEntitiesWithFireResistance.java @@ -0,0 +1,11 @@ +package org.dreeam.leaf.config.modules.gameplay.world; + +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 { + + @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..434e7df13d --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/world/RandomStrollIntoNonTickingChunks.java @@ -0,0 +1,11 @@ +package org.dreeam.leaf.config.modules.gameplay.world; + +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 { + + @ConfigInfo(name = "enabled") + public boolean enabled = true; +} 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/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 f06da7be8c..0000000000 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/SecureSeed.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.dreeam.leaf.config.modules.misc; - -import org.dreeam.leaf.config.ConfigModule; -import org.dreeam.leaf.config.ConfigCategory; - -public class SecureSeed extends ConfigModule { - - public String basePath() { - return ConfigCategory.MISC.basePath() + ".secure-seed"; - } - - public static boolean 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位的种子进行生成, 无法被破解."""); - - enabled = globalConfig.getBoolean(basePath() + ".enabled", enabled); - } -} 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/global/Chat.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/global/Chat.java new file mode 100644 index 0000000000..7f8590d47f --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/global/Chat.java @@ -0,0 +1,17 @@ +package org.dreeam.leaf.config.modules.misc.global; + +import org.dreeam.leaf.config.*; +import org.dreeam.leaf.config.annotations.*; + +@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..565f0e7488 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/global/LastTickTimeInTpsCommand.java @@ -0,0 +1,14 @@ +package org.dreeam.leaf.config.modules.misc.global; + +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 { + + @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..aa404fad75 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/global/LogToConsole.java @@ -0,0 +1,33 @@ +package org.dreeam.leaf.config.modules.misc.global; + +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 { + + @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..06619d6999 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/global/PluginLibraryLoader.java @@ -0,0 +1,25 @@ +package org.dreeam.leaf.config.modules.misc.global; + +import org.bukkit.plugin.java.JavaPluginLoader; +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 { + + @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/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 f2f9821658..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,33 +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 方法来优化非刷新数据包的发送, - 避免在调度数据包操作时进行昂贵的线程唤醒调用. - - 需要重启服务器才能生效.""")); - } } 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/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 d743cbd144..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,45 +72,15 @@ 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; } @Override - public void onPostLoaded() { + public void onRegistriesLoaded() { for (EntityType entityType : BuiltInRegistries.ENTITY_TYPE) { entityType.dabEnabled = true; // reset all, before setting the ones to true } @@ -85,7 +95,7 @@ public void onPostLoaded() { 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/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/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; 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) { + } +} 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 new file mode 100644 index 0000000000..7109003c01 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/util/ConfigPaths.java @@ -0,0 +1,31 @@ +package org.dreeam.leaf.config.util; + +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 ConfigPaths { + + 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.name().isEmpty() + ? info.category().basePath() + : 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/su/plo/matter/Globals.java b/leaf-server/src/main/java/su/plo/matter/Globals.java index 11bf11174b..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 (!org.dreeam.leaf.config.modules.misc.SecureSeed.enabled) return; + if (!world.leafConfig().secureSeed.enabled) return; long[] seed = world.getServer().getWorldGenSettings().options().featureSeed(); System.arraycopy(seed, 0, worldSeed, 0, WORLD_SEED_LONGS);