diff --git a/patches/api/0003-Suki-configuration.patch b/patches/api/0003-Suki-configuration.patch new file mode 100644 index 0000000..6f59765 --- /dev/null +++ b/patches/api/0003-Suki-configuration.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MartijnMuijsers +Date: Thu, 13 Oct 2022 20:55:09 +0200 +Subject: [PATCH] Suki configuration + +Original code by SuCraft, licensed under GPL-3.0. +You can find the original code on https://github.com/SuCraft/Suki + +diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java +index a7e2fab3e174fe2c1d79d904f7eaff88dd0c3c82..b36419e0512b4b7ef421b54ca27db37e02fbe86e 100644 +--- a/src/main/java/org/bukkit/Server.java ++++ b/src/main/java/org/bukkit/Server.java +@@ -1981,6 +1981,13 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi + } + // Purpur end + ++ // Suki start - Suki configuration ++ @NotNull ++ public org.bukkit.configuration.file.YamlConfiguration getSukiConfig() { ++ throw new UnsupportedOperationException("Not supported yet."); ++ } ++ // Suki end - Suki configuration ++ + /** + * Sends the component to the player + * diff --git a/patches/server/0014-Suki-Patches.patch b/patches/server/0014-Suki-Patches.patch new file mode 100644 index 0000000..cd1f5a8 --- /dev/null +++ b/patches/server/0014-Suki-Patches.patch @@ -0,0 +1,948 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MartijnMuijsers +Date: Thu, 13 Oct 2022 18:09:28 +0200 +Subject: [PATCH] Suki Patches + +Original code by SuCraft, licensed under GPL-3.0. +You can find the original code on https://github.com/SuCraft/Suki +0002-Suki-configuration-and-commands.patch +0012-Only-refresh-lootables-for-players.patch +0013-Optimize-harmless-explosions.patch +0016-Send-more-packets-immediately.patch +0017-Flush-after-more-packets.patch +0018-Do-not-relocate-corrupted-chunks.patch + +diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java +index 2cc44fbf8e5bd436b6d4e19f6c06b351e750cb31..991b06836df310913a14ce1c60979a2c2944b0e5 100644 +--- a/src/main/java/co/aikar/timings/TimingsExport.java ++++ b/src/main/java/co/aikar/timings/TimingsExport.java +@@ -29,6 +29,7 @@ import net.kyori.adventure.text.event.ClickEvent; + import net.kyori.adventure.text.format.NamedTextColor; + import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; + import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.dedicated.DedicatedServer; + import org.apache.commons.lang.StringUtils; + import org.bukkit.Bukkit; + import org.bukkit.Material; +@@ -242,7 +243,8 @@ public class TimingsExport extends Thread { + pair("spigot", mapAsJSON(Bukkit.spigot().getSpigotConfig(), null)), + pair("bukkit", mapAsJSON(Bukkit.spigot().getBukkitConfig(), null)), + pair("paper", mapAsJSON(Bukkit.spigot().getPaperConfig(), null)), // Pufferfish +- pair("pufferfish", mapAsJSON(gg.pufferfish.pufferfish.PufferfishConfig.getConfigCopy(), null)) // Pufferfish ++ pair("pufferfish", mapAsJSON(gg.pufferfish.pufferfish.PufferfishConfig.getConfigCopy(), null)), // Pufferfish ++ pair("suki", mapAsJSON(Bukkit.spigot().getSukiConfig(), null)) // Suki - Suki configuration + )); + + new TimingsExport(listeners, parent, history).start(); +diff --git a/src/main/java/io/papermc/paper/configuration/ConfigurationPart.java b/src/main/java/io/papermc/paper/configuration/ConfigurationPart.java +index 7a4a7a654fe2516ed894a68f2657344df9d70f4c..0a501c3ba5382dae230122066c131de631edff0d 100644 +--- a/src/main/java/io/papermc/paper/configuration/ConfigurationPart.java ++++ b/src/main/java/io/papermc/paper/configuration/ConfigurationPart.java +@@ -1,6 +1,6 @@ + package io.papermc.paper.configuration; + +-abstract class ConfigurationPart { ++public abstract class ConfigurationPart { // Suki - Suki configuration + + public static abstract class Post extends ConfigurationPart { + +diff --git a/src/main/java/io/papermc/paper/configuration/Configurations.java b/src/main/java/io/papermc/paper/configuration/Configurations.java +index c2dca89291361d60cbf160cab77749cb0130035a..aedd5aec308dd206ce149e2b303323e5a8adb88e 100644 +--- a/src/main/java/io/papermc/paper/configuration/Configurations.java ++++ b/src/main/java/io/papermc/paper/configuration/Configurations.java +@@ -5,7 +5,10 @@ import io.leangen.geantyref.TypeToken; + import io.papermc.paper.configuration.constraint.Constraint; + import io.papermc.paper.configuration.constraint.Constraints; + import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ServerLevel; ++import org.bukkit.configuration.ConfigurationSection; ++import org.bukkit.configuration.file.YamlConfiguration; + import org.checkerframework.checker.nullness.qual.Nullable; + import org.jetbrains.annotations.MustBeInvokedByOverriders; + import org.slf4j.Logger; +@@ -17,6 +20,8 @@ import org.spongepowered.configurate.objectmapping.ObjectMapper; + import org.spongepowered.configurate.serialize.SerializationException; + import org.spongepowered.configurate.util.CheckedFunction; + import org.spongepowered.configurate.yaml.YamlConfigurationLoader; ++import org.sucraft.suki.configuration.SukiConfigurations; ++import org.sucraft.suki.configuration.SukiWorldConfiguration; + + import java.io.IOException; + import java.lang.reflect.Type; +@@ -88,7 +93,7 @@ public abstract class Configurations { + }; + } + +- static CheckedFunction reloader(Class type, T instance) { ++ public static CheckedFunction reloader(Class type, T instance) { // Suki - Suki configuration + return node -> { + ObjectMapper.Factory factory = (ObjectMapper.Factory) Objects.requireNonNull(node.options().serializers().get(type)); + ObjectMapper.Mutable mutable = (ObjectMapper.Mutable) factory.get(type); +@@ -148,7 +153,7 @@ public abstract class Configurations { + final YamlConfigurationLoader loader = result.loader(); + final ConfigurationNode node = loader.load(); + if (result.isNewFile()) { // add version to new files +- node.node(Configuration.VERSION_FIELD).raw(WorldConfiguration.CURRENT_VERSION); ++ node.node(Configuration.VERSION_FIELD).raw(getWorldConfigurationCurrentVersion()); // Suki - Suki configuration + } + this.applyWorldConfigTransformations(contextMap, node); + final W instance = node.require(this.worldConfigClass); +@@ -207,7 +212,7 @@ public abstract class Configurations { + .build(); + final ConfigurationNode worldNode = worldLoader.load(); + if (newFile) { // set the version field if new file +- worldNode.node(Configuration.VERSION_FIELD).set(WorldConfiguration.CURRENT_VERSION); ++ worldNode.node(Configuration.VERSION_FIELD).set(getWorldConfigurationCurrentVersion()); // Suki - Suki configuration + } + this.applyWorldConfigTransformations(contextMap, worldNode); + this.applyDefaultsAwareWorldConfigTransformations(contextMap, worldNode, defaultsNode); +@@ -308,4 +313,21 @@ public abstract class Configurations { + return "ContextKey{" + this.name + "}"; + } + } ++ ++ // Suki start - Suki configuration ++ ++ @Deprecated ++ public YamlConfiguration createLegacyObject(final MinecraftServer server) { ++ YamlConfiguration global = YamlConfiguration.loadConfiguration(this.globalFolder.resolve(this.globalConfigFileName).toFile()); ++ ConfigurationSection worlds = global.createSection("__________WORLDS__________"); ++ worlds.set("__defaults__", YamlConfiguration.loadConfiguration(this.globalFolder.resolve(this.defaultWorldConfigFileName).toFile())); ++ for (ServerLevel level : server.getAllLevels()) { ++ worlds.set(level.getWorld().getName(), YamlConfiguration.loadConfiguration(getWorldConfigFile(level).toFile())); ++ } ++ return global; ++ } ++ ++ public abstract int getWorldConfigurationCurrentVersion(); ++ // Suki end - Suki configuration ++ + } +diff --git a/src/main/java/io/papermc/paper/configuration/InnerClassFieldDiscoverer.java b/src/main/java/io/papermc/paper/configuration/InnerClassFieldDiscoverer.java +index a0aa1f1a7adf986d500a2135aa42e138aa3c4f08..bc3907beb2aa63259300e8960039bd59397d071e 100644 +--- a/src/main/java/io/papermc/paper/configuration/InnerClassFieldDiscoverer.java ++++ b/src/main/java/io/papermc/paper/configuration/InnerClassFieldDiscoverer.java +@@ -5,6 +5,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; + import org.spongepowered.configurate.objectmapping.FieldDiscoverer; + import org.spongepowered.configurate.serialize.SerializationException; + import org.spongepowered.configurate.util.CheckedSupplier; ++import org.sucraft.suki.configuration.SukiWorldConfiguration; + + import java.lang.reflect.AnnotatedType; + import java.lang.reflect.Constructor; +@@ -17,7 +18,7 @@ import java.util.Map; + + import static io.leangen.geantyref.GenericTypeReflector.erase; + +-final class InnerClassFieldDiscoverer implements FieldDiscoverer> { ++public final class InnerClassFieldDiscoverer implements FieldDiscoverer> { // Suki - Suki configuration + + private final Map, Object> instanceMap = new HashMap<>(); + private final Map, Object> overrides; +@@ -136,7 +137,19 @@ final class InnerClassFieldDiscoverer implements FieldDiscoverer globalConfig() { ++ // Suki start - Suki configuration ++ public static FieldDiscoverer sukiWorldConfig(Configurations.ContextMap contextMap) { ++ final Map, Object> overrides = Map.of( ++ SukiWorldConfiguration.class, new SukiWorldConfiguration( ++ contextMap.require(PaperConfigurations.SPIGOT_WORLD_CONFIG_CONTEXT_KEY).get(), ++ contextMap.require(Configurations.WORLD_KEY) ++ ) ++ ); ++ return new InnerClassFieldDiscoverer(overrides); ++ } ++ // Suki end - Suki configuration ++ ++ public static FieldDiscoverer globalConfig() { // Suki - Suki configuration + return new InnerClassFieldDiscoverer(Collections.emptyMap()); + } + } +diff --git a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java +index e731de3ac158c5a4cff236c6f5001674cd488a77..06a6cecfa01c676285ea894c3ed77d0e2d96ba91 100644 +--- a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java ++++ b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java +@@ -125,13 +125,13 @@ public class PaperConfigurations extends Configurations SPIGOT_WORLD_DEFAULTS = Suppliers.memoize(() -> new SpigotWorldConfig(RandomStringUtils.randomAlphabetic(255)) { ++ public static final Supplier SPIGOT_WORLD_DEFAULTS = Suppliers.memoize(() -> new SpigotWorldConfig(RandomStringUtils.randomAlphabetic(255)) { // Suki - Suki configuration + @Override // override to ensure "verbose" is false + public void init() { + SpigotConfig.readConfig(SpigotWorldConfig.class, this); + } + }); +- static final ContextKey> SPIGOT_WORLD_CONFIG_CONTEXT_KEY = new ContextKey<>(new TypeToken>() {}, "spigot world config"); ++ public static final ContextKey> SPIGOT_WORLD_CONFIG_CONTEXT_KEY = new ContextKey<>(new TypeToken>() {}, "spigot world config"); // Suki - Suki configuration + + + public PaperConfigurations(final Path globalFolder) { +@@ -293,7 +293,7 @@ public class PaperConfigurations extends Configurations> { + packet instanceof net.minecraft.network.protocol.game.ClientboundSetActionBarTextPacket || + packet instanceof net.minecraft.network.protocol.game.ClientboundSetTitlesAnimationPacket || + packet instanceof net.minecraft.network.protocol.game.ClientboundClearTitlesPacket || +- packet instanceof net.minecraft.network.protocol.game.ClientboundBossEventPacket; ++ // Suki start - send more packets immediately ++ packet instanceof net.minecraft.network.protocol.game.ClientboundBossEventPacket || ++ packet instanceof net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket || ++ packet instanceof net.minecraft.network.protocol.game.ClientboundMapItemDataPacket; ++ // Suki end - send more packets immediately + } + // Paper end + } +@@ -406,7 +410,15 @@ public class Connection extends SimpleChannelInboundHandler> { + private void sendPacket(Packet packet, @Nullable PacketSendListener callbacks, Boolean flushConditional) { + this.packetWrites.getAndIncrement(); // must be befeore using canFlush + boolean effectiveFlush = flushConditional == null ? this.canFlush : flushConditional.booleanValue(); +- final boolean flush = effectiveFlush || packet instanceof net.minecraft.network.protocol.game.ClientboundKeepAlivePacket || packet instanceof ClientboundDisconnectPacket; // no delay for certain packets ++ // Suki start - flush after more packets ++ final boolean flush = effectiveFlush || ++ packet instanceof net.minecraft.network.protocol.game.ClientboundKeepAlivePacket || ++ packet instanceof ClientboundDisconnectPacket || ++ packet instanceof net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket || ++ packet instanceof net.minecraft.network.protocol.game.ClientboundForgetLevelChunkPacket || ++ packet instanceof net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket || ++ packet instanceof net.minecraft.network.protocol.game.ClientboundMapItemDataPacket; // no delay for certain packets ++ // Suki end - flush after more packets + // Paper end - add flush parameter + ConnectionProtocol enumprotocol = ConnectionProtocol.getProtocolForPacket(packet); + ConnectionProtocol enumprotocol1 = this.getCurrentProtocol(); +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index a444c8c57a2ab16eba45b0379841a69ff60a5586..d7be9d4e29347b492c22ed2830afc59cffe2d2bc 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -294,6 +294,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop public + + public static Services create(YggdrasilAuthenticationService authenticationService, File rootDirectory, File userCacheFile, joptsimple.OptionSet optionSet) throws Exception { // Paper +@@ -30,7 +36,11 @@ public record Services(MinecraftSessionService sessionService, SignatureValidato + final java.nio.file.Path legacyConfigPath = ((File) optionSet.valueOf("paper-settings")).toPath(); + final java.nio.file.Path configDirPath = ((File) optionSet.valueOf("paper-settings-directory")).toPath(); + io.papermc.paper.configuration.PaperConfigurations paperConfigurations = io.papermc.paper.configuration.PaperConfigurations.setup(legacyConfigPath, configDirPath, rootDirectory.toPath(), (File) optionSet.valueOf("spigot-settings")); +- return new Services(minecraftSessionService, signatureValidator, gameProfileRepository, gameProfileCache, paperConfigurations); ++ // Suki start - Suki configuration ++ final java.nio.file.Path sukiLegacyConfigPath = legacyConfigPath.resolveSibling("suki.yml"); ++ org.sucraft.suki.configuration.SukiConfigurations sukiConfigurations = org.sucraft.suki.configuration.SukiConfigurations.setup(sukiLegacyConfigPath, configDirPath, rootDirectory.toPath(), (File) optionSet.valueOf("spigot-settings")); ++ return new Services(minecraftSessionService, signatureValidator, gameProfileRepository, gameProfileCache, paperConfigurations, sukiConfigurations); ++ // Suki end - Suki configuration + // Paper end + } + } +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index 383045ae681d5366761c807a0032156203cdf9c9..3e7dd41555263e8361db08d1380c767fe0ba8886 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -213,6 +213,10 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + io.papermc.paper.util.ObfHelper.INSTANCE.getClass(); // Paper - load mappings for stacktrace deobf and etc. + paperConfigurations.initializeGlobalConfiguration(); + paperConfigurations.initializeWorldDefaultsConfiguration(); ++ // Suki start - Suki configuration ++ sukiConfigurations.initializeGlobalConfiguration(); ++ sukiConfigurations.initializeWorldDefaultsConfiguration(); ++ // Suki end - Suki configuration + // Paper start - moved up to right after PlayerList creation but before file load/save + if (this.convertOldUsers()) { + this.getProfileCache().save(false); // Paper +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java +index 01ca7156d86243a80cd343a2a66be9ebedcc3b7c..852ec490e2a53803ca72a3ec0d994cbefb1f5fcd 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java +@@ -97,7 +97,7 @@ public class DedicatedServerProperties extends Settings.MutableValue playerIdleTimeout; + public final Settings.MutableValue whiteList; + public final boolean enforceSecureProfile; +- private final DedicatedServerProperties.WorldGenProperties worldGenProperties; ++ public final DedicatedServerProperties.WorldGenProperties worldGenProperties; // Suki - Suki configuration- include vanilla server.properties in timings + @Nullable + private WorldGenSettings worldGenSettings; + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 2a9219e0139670674319a7cf17ad664582b42715..ba95c8ba2fdcb73065c2fc219f94528239751f33 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -533,7 +533,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) { + // Holder holder = worlddimension.typeHolder(); // CraftBukkit - decompile error + // Objects.requireNonNull(minecraftserver); // CraftBukkit - decompile error +- super(iworlddataserver, resourcekey, worlddimension.typeHolder(), minecraftserver::getProfiler, false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> minecraftserver.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig)), executor); // Paper - Async-Anti-Xray - Pass executor ++ super(iworlddataserver, resourcekey, worlddimension.typeHolder(), minecraftserver::getProfiler, false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> minecraftserver.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig)), spigotConfig -> minecraftserver.sukiConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig)), executor); // Paper - Async-Anti-Xray - Pass executor // Suki - Suki configuration + this.pvpMode = minecraftserver.isPvpAllowed(); + this.convertable = convertable_conversionsession; + this.uuid = WorldUUID.getUUID(convertable_conversionsession.levelDirectory.path().toFile()); +diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java +index c9def2202d7c2a523858ec124df2beaf994d9888..3a7bcee2e4b1c4ef2ba303b0200ae846357c5982 100644 +--- a/src/main/java/net/minecraft/world/level/Explosion.java ++++ b/src/main/java/net/minecraft/world/level/Explosion.java +@@ -152,6 +152,7 @@ public class Explosion { + int i; + int j; + ++ if (this.fire || this.blockInteraction != BlockInteraction.NONE) { // Suki - optimize explosions - don't run block processing if neither flag is set + for (int k = 0; k < 16; ++k) { + for (i = 0; i < 16; ++i) { + for (j = 0; j < 16; ++j) { +@@ -191,8 +192,8 @@ public class Explosion { + if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowHeadlessPistons && iblockdata.getBlock() == Blocks.MOVING_PISTON) { + BlockEntity extension = this.level.getBlockEntity(blockposition); + if (extension instanceof net.minecraft.world.level.block.piston.PistonMovingBlockEntity blockEntity && blockEntity.isSourcePiston()) { +- net.minecraft.core.Direction direction = iblockdata.getValue(net.minecraft.world.level.block.piston.PistonHeadBlock.FACING); +- set.add(blockposition.relative(direction.getOpposite())); ++ net.minecraft.core.Direction direction = iblockdata.getValue(net.minecraft.world.level.block.piston.PistonHeadBlock.FACING); ++ set.add(blockposition.relative(direction.getOpposite())); + } + } + // Paper end +@@ -206,6 +207,7 @@ public class Explosion { + } + } + } ++ } // Suki - optimize explosions - don't run block processing if neither flag is set + + this.toBlow.addAll(set); + float f2 = this.radius * 2.0F; +@@ -296,7 +298,7 @@ public class Explosion { + boolean flag1 = this.blockInteraction != Explosion.BlockInteraction.NONE; + + if (particles) { +- if (this.radius >= 2.0F && flag1) { ++ if (this.radius >= 2.0F) { + this.level.addParticle(ParticleTypes.EXPLOSION_EMITTER, this.x, this.y, this.z, 1.0D, 0.0D, 0.0D); + } else { + this.level.addParticle(ParticleTypes.EXPLOSION, this.x, this.y, this.z, 1.0D, 0.0D, 0.0D); +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 9f0126aedb94f74d01dc47112496160cc4e1f02c..dc3f2b26e19619adf80827d26b95209ded973c2a 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -172,7 +172,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + return this.paperConfig; + } + // Paper end ++ // Suki start - Suki configuration ++ private final org.sucraft.suki.configuration.SukiWorldConfiguration sukiConfig; + ++ public org.sucraft.suki.configuration.SukiWorldConfiguration sukiConfig() { ++ return this.sukiConfig; ++ } ++ ++ // Suki end - Suki configuration + public final com.destroystokyo.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray + public final org.purpurmc.purpur.PurpurWorldConfig purpurConfig; // Purpur + +@@ -327,10 +334,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + @Override public final int getHeight() { return this.height; } + // Pufferfish end + +- protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, Holder holder, Supplier supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function paperWorldConfigCreator, java.util.concurrent.Executor executor) { // Paper - Async-Anti-Xray - Pass executor ++ protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, Holder holder, Supplier supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function paperWorldConfigCreator, java.util.function.Function sukiWorldConfigCreator, java.util.concurrent.Executor executor) { // Paper - Async-Anti-Xray - Pass executor // Suki - Suki configuration + this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot + this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper + this.purpurConfig = new org.purpurmc.purpur.PurpurWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName(), env); // Purpur ++ this.sukiConfig = sukiWorldConfigCreator.apply(this.spigotConfig); // Suki - Suki configuration + this.playerBreedingCooldowns = this.getNewBreedingCooldownCache(); // Purpur + this.generator = gen; + this.world = new CraftWorld((ServerLevel) this, gen, biomeProvider, env); +diff --git a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java +index 13e749a3c40f0b2cc002f13675a9a56eedbefdac..01ea0068e1c5a2bf0ad18bfa58bc98a7a2a98ef7 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java +@@ -69,6 +69,15 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc + } + + public void unpackLootTable(@Nullable Player player) { ++ // Suki start - only refresh lootables for players ++ if (this.level.getServer() != null) { ++ if (this.level.sukiConfig().lootables.onlyRefreshForPlayers) { ++ if (player == null) { ++ return; ++ } ++ } ++ } ++ // Suki end - only refresh lootables for players + if (this.lootableData.shouldReplenish(player) && this.level.getServer() != null) { // Paper + LootTable lootTable = this.level.getServer().getLootTables().get(this.lootTable); + if (player instanceof ServerPlayer) { +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +index 34e351e04ac57e47e3cea671c61cc01d17983b77..f583ae81fb60027c47aaecdb6451462cd0d72936 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +@@ -30,6 +30,7 @@ import net.minecraft.nbt.NbtOps; + import net.minecraft.nbt.ShortTag; + import net.minecraft.nbt.Tag; + import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ServerChunkCache; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ThreadedLevelLightEngine; +@@ -157,7 +158,14 @@ public class ChunkSerializer { + ChunkPos chunkcoordintpair1 = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos")); // Paper - diff on change, see ChunkSerializer#getChunkCoordinate + + if (!Objects.equals(chunkPos, chunkcoordintpair1)) { +- ChunkSerializer.LOGGER.error("Chunk file at {} is in the wrong location; relocating. (Expected {}, got {})", new Object[]{chunkPos, chunkPos, chunkcoordintpair1}); ++ // Suki start - do not relocate corrupted chunks ++ if (world.sukiConfig().relocateCorruptedChunks.enabled) { ++ ChunkSerializer.LOGGER.error("Chunk file at {} is in the wrong location; relocating. (Expected {}, got {})", new Object[]{chunkPos, chunkPos, chunkcoordintpair1}); ++ } else { ++ ChunkSerializer.LOGGER.error("Chunk file at {} is in the wrong location; stopping the server. (Expected {}, got {})", new Object[]{chunkPos, chunkPos, chunkcoordintpair1}); ++ MinecraftServer.getServer().stopServer(); ++ } ++ // Suki end - do not relocate corrupted chunks + } + + UpgradeData chunkconverter = nbt.contains("UpgradeData", 10) ? new UpgradeData(nbt.getCompound("UpgradeData"), world) : UpgradeData.EMPTY; +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 4f029b96bf8deee597dbb56974e4519a1422f96d..fb28dbf080634bb5e421f08ed117cc2588e5c2a1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1013,6 +1013,7 @@ public final class CraftServer implements Server { + org.spigotmc.SpigotConfig.init((File) console.options.valueOf("spigot-settings")); // Spigot + this.console.paperConfigurations.reloadConfigs(this.console); + org.purpurmc.purpur.PurpurConfig.init((File) console.options.valueOf("purpur-settings")); // Purpur ++ this.console.sukiConfigurations.reloadConfigs(this.console); // Suki - Suki configuration + for (ServerLevel world : this.console.getAllLevels()) { + // world.serverLevelData.setDifficulty(config.difficulty); // Paper - per level difficulty + world.setSpawnSettings(world.serverLevelData.getDifficulty() != Difficulty.PEACEFUL && config.spawnMonsters, config.spawnAnimals); // Paper - per level difficulty (from MinecraftServer#setDifficulty(ServerLevel, Difficulty, boolean)) +@@ -2840,6 +2841,13 @@ public final class CraftServer implements Server { + return CraftServer.this.console.paperConfigurations.createLegacyObject(CraftServer.this.console); + } + ++ // Suki start - Suki configuration ++ @Override ++ public YamlConfiguration getSukiConfig() { ++ return CraftServer.this.console.sukiConfigurations.createLegacyObject(CraftServer.this.console); ++ } ++ // Suki end - Suki configuration ++ + // Purpur start + @Override + public YamlConfiguration getPurpurConfig() { +diff --git a/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java b/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java +index 8e06bc11fb28baee3407bbfe9d7b3689d6f85ff2..ce1190a6b0fb1e4f00e0d573edd42e2456c7e1e2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java +@@ -5,6 +5,7 @@ import org.bukkit.util.permissions.DefaultPermissions; + + public final class CraftDefaultPermissions { + private static final String ROOT = "minecraft"; ++ private static final String SUKI_ROOT = "suki"; // Suki - Suki permissions + + private CraftDefaultPermissions() {} + +diff --git a/src/main/java/org/sucraft/suki/configuration/SukiConfigurations.java b/src/main/java/org/sucraft/suki/configuration/SukiConfigurations.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b64077fa2ff5c85b7c07868841a133f9e1b20c15 +--- /dev/null ++++ b/src/main/java/org/sucraft/suki/configuration/SukiConfigurations.java +@@ -0,0 +1,296 @@ ++// Suki - Suki configuration ++ ++package org.sucraft.suki.configuration; ++ ++import com.google.common.collect.Table; ++import com.mojang.logging.LogUtils; ++import io.leangen.geantyref.TypeToken; ++import io.papermc.paper.configuration.Configuration; ++import io.papermc.paper.configuration.ConfigurationPart; ++import io.papermc.paper.configuration.Configurations; ++import io.papermc.paper.configuration.InnerClassFieldDiscoverer; ++import io.papermc.paper.configuration.NestedSetting; ++import io.papermc.paper.configuration.PaperConfigurations; ++import io.papermc.paper.configuration.legacy.RequiresSpigotInitialization; ++import io.papermc.paper.configuration.serializer.ComponentSerializer; ++import io.papermc.paper.configuration.serializer.EnumValueSerializer; ++import io.papermc.paper.configuration.serializer.FastutilMapSerializer; ++import io.papermc.paper.configuration.serializer.PacketClassSerializer; ++import io.papermc.paper.configuration.serializer.StringRepresentableSerializer; ++import io.papermc.paper.configuration.serializer.TableSerializer; ++import io.papermc.paper.configuration.serializer.collections.MapSerializer; ++import io.papermc.paper.configuration.serializer.registry.RegistryHolderSerializer; ++import io.papermc.paper.configuration.serializer.registry.RegistryValueSerializer; ++import io.papermc.paper.configuration.transformation.Transformations; ++import io.papermc.paper.configuration.type.BooleanOrDefault; ++import io.papermc.paper.configuration.type.DoubleOrDefault; ++import io.papermc.paper.configuration.type.Duration; ++import io.papermc.paper.configuration.type.EngineMode; ++import io.papermc.paper.configuration.type.IntOr; ++import io.papermc.paper.configuration.type.fallback.FallbackValueSerializer; ++import it.unimi.dsi.fastutil.objects.Reference2IntMap; ++import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; ++import it.unimi.dsi.fastutil.objects.Reference2LongMap; ++import it.unimi.dsi.fastutil.objects.Reference2LongOpenHashMap; ++import net.minecraft.core.Registry; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.entity.EntityType; ++import net.minecraft.world.item.Item; ++import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; ++import org.slf4j.Logger; ++import org.spongepowered.configurate.ConfigurateException; ++import org.spongepowered.configurate.ConfigurationNode; ++import org.spongepowered.configurate.ConfigurationOptions; ++import org.spongepowered.configurate.NodePath; ++import org.spongepowered.configurate.objectmapping.ObjectMapper; ++import org.spongepowered.configurate.transformation.ConfigurationTransformation; ++import org.spongepowered.configurate.transformation.TransformAction; ++import org.spongepowered.configurate.yaml.YamlConfigurationLoader; ++ ++import java.io.File; ++import java.io.IOException; ++import java.lang.reflect.Type; ++import java.nio.file.Files; ++import java.nio.file.Path; ++import java.util.Collections; ++import java.util.List; ++import java.util.function.Function; ++ ++import static io.leangen.geantyref.GenericTypeReflector.erase; ++ ++@SuppressWarnings("Convert2Diamond") ++public class SukiConfigurations extends Configurations { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ static final String GLOBAL_CONFIG_FILE_NAME = "suki-global.yml"; ++ static final String WORLD_DEFAULTS_CONFIG_FILE_NAME = "suki-world-defaults.yml"; ++ static final String WORLD_CONFIG_FILE_NAME = "suki-world.yml"; ++ public static final String CONFIG_DIR = "config"; ++ ++ private static final String GLOBAL_HEADER = String.format(""" ++ This is the global configuration file for Suki. ++ As you can see, there's a lot to configure. Some options may impact gameplay, so use ++ with caution, and make sure you know what each option does before configuring. ++ ++ If you need help with the configuration or have any questions related to Suki, ++ join us in our Discord for SuCraft, or check the GitHub Wiki pages. ++ ++ The world configuration options are inside ++ their respective world folder. The files are named %s ++ ++ Wiki: https://github.com/SuCraft/Suki/wiki ++ Discord: https://discord.com/invite/pbsPkpUjG4""", WORLD_CONFIG_FILE_NAME); ++ ++ private static final String WORLD_DEFAULTS_HEADER = """ ++ This is the world defaults configuration file for Suki. ++ As you can see, there's a lot to configure. Some options may impact gameplay, so use ++ with caution, and make sure you know what each option does before configuring. ++ ++ If you need help with the configuration or have any questions related to Suki, ++ join us in our Discord for SuCraft, or check the GitHub Wiki pages. ++ ++ Configuration options here apply to all worlds, unless you specify overrides inside ++ the world-specific config file inside each world folder. ++ ++ Wiki: https://github.com/SuCraft/Suki/wiki ++ Discord: https://discord.com/invite/pbsPkpUjG4"""; ++ ++ private static final Function WORLD_HEADER = map -> String.format(""" ++ This is a world configuration file for Suki. ++ This file may start empty but can be filled with settings to override ones in the %s/%s ++ ++ World: %s (%s)""", ++ CONFIG_DIR, ++ WORLD_DEFAULTS_CONFIG_FILE_NAME, ++ map.require(WORLD_NAME), ++ map.require(WORLD_KEY) ++ ); ++ ++ private static final String MOVED_NOTICE = """ ++ The global and world default configuration files have moved to %s ++ and the world-specific configuration file has been moved inside ++ the respective world folder. ++ ++ See https://github.com/SuCraft/Suki/wiki for more information. ++ """; ++ ++ public SukiConfigurations(final Path globalFolder) { ++ super(globalFolder, SukiGlobalConfiguration.class, SukiWorldConfiguration.class, GLOBAL_CONFIG_FILE_NAME, WORLD_DEFAULTS_CONFIG_FILE_NAME, WORLD_CONFIG_FILE_NAME); ++ } ++ ++ @Override ++ protected YamlConfigurationLoader.Builder createLoaderBuilder() { ++ return super.createLoaderBuilder() ++ .defaultOptions(SukiConfigurations::defaultOptions); ++ } ++ ++ private static ConfigurationOptions defaultOptions(ConfigurationOptions options) { ++ return options.serializers(builder -> builder ++ .register(MapSerializer.TYPE, new MapSerializer(false)) ++ .register(new EnumValueSerializer()) ++ .register(new ComponentSerializer()) ++ ); ++ } ++ ++ @Override ++ protected ObjectMapper.Factory.Builder createGlobalObjectMapperFactoryBuilder() { ++ return defaultGlobalFactoryBuilder(super.createGlobalObjectMapperFactoryBuilder()); ++ } ++ ++ private static ObjectMapper.Factory.Builder defaultGlobalFactoryBuilder(ObjectMapper.Factory.Builder builder) { ++ return builder.addDiscoverer(InnerClassFieldDiscoverer.globalConfig()); ++ } ++ ++ @Override ++ protected YamlConfigurationLoader.Builder createGlobalLoaderBuilder() { ++ return super.createGlobalLoaderBuilder() ++ .defaultOptions(SukiConfigurations::defaultGlobalOptions); ++ } ++ ++ private static ConfigurationOptions defaultGlobalOptions(ConfigurationOptions options) { ++ return options ++ .header(GLOBAL_HEADER) ++ .serializers(builder -> builder.register(new PacketClassSerializer())); ++ } ++ ++ @Override ++ public SukiGlobalConfiguration initializeGlobalConfiguration() throws ConfigurateException { ++ SukiGlobalConfiguration configuration = super.initializeGlobalConfiguration(); ++ SukiGlobalConfiguration.set(configuration); ++ return configuration; ++ } ++ ++ @Override ++ protected ContextMap.Builder createDefaultContextMap() { ++ return super.createDefaultContextMap() ++ .put(PaperConfigurations.SPIGOT_WORLD_CONFIG_CONTEXT_KEY, PaperConfigurations.SPIGOT_WORLD_DEFAULTS); ++ } ++ ++ @Override ++ protected ObjectMapper.Factory.Builder createWorldObjectMapperFactoryBuilder(final ContextMap contextMap) { ++ return super.createWorldObjectMapperFactoryBuilder(contextMap) ++ .addNodeResolver(new RequiresSpigotInitialization.Factory(contextMap.require(PaperConfigurations.SPIGOT_WORLD_CONFIG_CONTEXT_KEY).get())) ++ .addNodeResolver(new NestedSetting.Factory()) ++ .addDiscoverer(InnerClassFieldDiscoverer.sukiWorldConfig(contextMap)); ++ } ++ ++ @Override ++ protected YamlConfigurationLoader.Builder createWorldConfigLoaderBuilder(final ContextMap contextMap) { ++ return super.createWorldConfigLoaderBuilder(contextMap) ++ .defaultOptions(options -> options ++ .header(contextMap.require(WORLD_NAME).equals(WORLD_DEFAULTS) ? WORLD_DEFAULTS_HEADER : WORLD_HEADER.apply(contextMap)) ++ .serializers(serializers -> serializers ++ .register(new TypeToken>() {}, new FastutilMapSerializer.SomethingToPrimitive>(Reference2IntOpenHashMap::new, Integer.TYPE)) ++ .register(new TypeToken>() {}, new FastutilMapSerializer.SomethingToPrimitive>(Reference2LongOpenHashMap::new, Long.TYPE)) ++ .register(new TypeToken>() {}, new TableSerializer()) ++ .register(new StringRepresentableSerializer()) ++ .register(IntOr.Default.SERIALIZER) ++ .register(DoubleOrDefault.SERIALIZER) ++ .register(BooleanOrDefault.SERIALIZER) ++ .register(Duration.SERIALIZER) ++ .register(EngineMode.SERIALIZER) ++ .register(FallbackValueSerializer.create(contextMap.require(PaperConfigurations.SPIGOT_WORLD_CONFIG_CONTEXT_KEY).get(), MinecraftServer::getServer)) ++ .register(new RegistryValueSerializer<>(new TypeToken>() {}, Registry.ENTITY_TYPE_REGISTRY, true)) ++ .register(new RegistryValueSerializer<>(Item.class, Registry.ITEM_REGISTRY, true)) ++ .register(new RegistryHolderSerializer<>(new TypeToken>() {}, Registry.CONFIGURED_FEATURE_REGISTRY, false)) ++ .register(new RegistryHolderSerializer<>(Item.class, Registry.ITEM_REGISTRY, true)) ++ ) ++ ); ++ } ++ ++ @Override ++ protected void applyWorldConfigTransformations(final ContextMap contextMap, final ConfigurationNode node) throws ConfigurateException { ++ final ConfigurationNode version = node.node(Configuration.VERSION_FIELD); ++ final String world = contextMap.require(WORLD_NAME); ++ if (version.virtual()) { ++ LOGGER.warn("The Suki world config file for " + world + " didn't have a version set, assuming latest"); ++ version.raw(SukiWorldConfiguration.CURRENT_VERSION); ++ } ++ if (SukiRemovedConfigurations.REMOVED_WORLD_PATHS.length > 0) { ++ ConfigurationTransformation.Builder builder = ConfigurationTransformation.builder(); ++ for (NodePath path : SukiRemovedConfigurations.REMOVED_WORLD_PATHS) { ++ builder.addAction(path, TransformAction.remove()); ++ } ++ builder.build().apply(node); ++ } ++ // ADD FUTURE TRANSFORMS HERE ++ } ++ ++ @Override ++ protected void applyGlobalConfigTransformations(ConfigurationNode node) throws ConfigurateException { ++ if (SukiRemovedConfigurations.REMOVED_GLOBAL_PATHS.length > 0) { ++ ConfigurationTransformation.Builder builder = ConfigurationTransformation.builder(); ++ for (NodePath path : SukiRemovedConfigurations.REMOVED_GLOBAL_PATHS) { ++ builder.addAction(path, TransformAction.remove()); ++ } ++ builder.build().apply(node); ++ } ++ // ADD FUTURE TRANSFORMS HERE ++ } ++ ++ private static final List DEFAULT_AWARE_TRANSFORMATIONS = Collections.emptyList(); ++ ++ @Override ++ protected void applyDefaultsAwareWorldConfigTransformations(final ContextMap contextMap, final ConfigurationNode worldNode, final ConfigurationNode defaultsNode) throws ConfigurateException { ++ final ConfigurationTransformation.Builder builder = ConfigurationTransformation.builder(); ++ // ADD FUTURE TRANSFORMS HERE (these transforms run after the defaults have been merged into the node) ++ DEFAULT_AWARE_TRANSFORMATIONS.forEach(transform -> transform.apply(builder, contextMap, defaultsNode)); ++ ++ ConfigurationTransformation transformation; ++ try { ++ transformation = builder.build(); // build throws IAE if no actions were provided (bad zml) ++ } catch (IllegalArgumentException ignored) { ++ return; ++ } ++ transformation.apply(worldNode); ++ } ++ ++ @Override ++ public SukiWorldConfiguration createWorldConfig(final ContextMap contextMap) { ++ final String levelName = contextMap.require(WORLD_NAME); ++ try { ++ return super.createWorldConfig(contextMap); ++ } catch (IOException exception) { ++ throw new RuntimeException("Could not create Suki world config for " + levelName, exception); ++ } ++ } ++ ++ @Override ++ protected boolean isConfigType(final Type type) { ++ return ConfigurationPart.class.isAssignableFrom(erase(type)); ++ } ++ ++ public void reloadConfigs(MinecraftServer server) { ++ try { ++ this.initializeGlobalConfiguration(reloader(this.globalConfigClass, SukiGlobalConfiguration.get())); ++ this.initializeWorldDefaultsConfiguration(); ++ for (ServerLevel level : server.getAllLevels()) { ++ this.createWorldConfig(PaperConfigurations.createWorldContextMap(level), reloader(this.worldConfigClass, level.sukiConfig())); ++ } ++ } catch (Exception ex) { ++ throw new RuntimeException("Could not reload Suki configuration files", ex); ++ } ++ } ++ ++ public static SukiConfigurations setup(final Path legacyConfig, final Path configDir, final Path worldFolder, final File spigotConfig) throws Exception { ++ final Path legacy = Files.isSymbolicLink(legacyConfig) ? Files.readSymbolicLink(legacyConfig) : legacyConfig; ++ final Path replacementFile = legacy.resolveSibling(legacyConfig.getFileName() + "-README.txt"); ++ if (Files.notExists(replacementFile)) { ++ Files.createFile(replacementFile); ++ Files.writeString(replacementFile, String.format(MOVED_NOTICE, configDir.toAbsolutePath())); ++ } ++ try { ++ PaperConfigurations.createDirectoriesSymlinkAware(configDir); ++ return new SukiConfigurations(configDir); ++ } catch (final IOException ex) { ++ throw new RuntimeException("Could not setup SukiConfigurations", ex); ++ } ++ } ++ ++ @Override ++ public int getWorldConfigurationCurrentVersion() { ++ return SukiWorldConfiguration.CURRENT_VERSION; ++ } ++ ++} +diff --git a/src/main/java/org/sucraft/suki/configuration/SukiGlobalConfiguration.java b/src/main/java/org/sucraft/suki/configuration/SukiGlobalConfiguration.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ba7c3818d17db725e6c39f4e0837b1fd1a3d0dfb +--- /dev/null ++++ b/src/main/java/org/sucraft/suki/configuration/SukiGlobalConfiguration.java +@@ -0,0 +1,31 @@ ++// Suki - Suki configuration ++ ++package org.sucraft.suki.configuration; ++ ++import io.papermc.paper.configuration.Configuration; ++import io.papermc.paper.configuration.ConfigurationPart; ++import org.spongepowered.configurate.objectmapping.meta.Setting; ++ ++@SuppressWarnings({"CanBeFinal", "FieldCanBeLocal", "FieldMayBeFinal", "NotNullFieldNotInitialized", "InnerClassMayBeStatic"}) ++public class SukiGlobalConfiguration extends ConfigurationPart { ++ static final int CURRENT_VERSION = 18; ++ private static SukiGlobalConfiguration instance; ++ public static SukiGlobalConfiguration get() { ++ return instance; ++ } ++ static void set(SukiGlobalConfiguration instance) { ++ SukiGlobalConfiguration.instance = instance; ++ } ++ ++ @Setting(Configuration.VERSION_FIELD) ++ public int version = CURRENT_VERSION; ++ ++ public GlobalDummyPart globalDummyPart; ++ ++ public class GlobalDummyPart extends ConfigurationPart { ++ ++ int globalDummyValue = 0; ++ ++ } ++ ++} +diff --git a/src/main/java/org/sucraft/suki/configuration/SukiRemovedConfigurations.java b/src/main/java/org/sucraft/suki/configuration/SukiRemovedConfigurations.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b4fd4190b899d4dea9420074d96e6431c2a3fafb +--- /dev/null ++++ b/src/main/java/org/sucraft/suki/configuration/SukiRemovedConfigurations.java +@@ -0,0 +1,13 @@ ++// Suki - Suki configuration ++ ++package org.sucraft.suki.configuration; ++ ++import org.spongepowered.configurate.NodePath; ++ ++interface SukiRemovedConfigurations { ++ ++ NodePath[] REMOVED_WORLD_PATHS = {}; ++ ++ NodePath[] REMOVED_GLOBAL_PATHS = {}; ++ ++} +diff --git a/src/main/java/org/sucraft/suki/configuration/SukiWorldConfiguration.java b/src/main/java/org/sucraft/suki/configuration/SukiWorldConfiguration.java +new file mode 100644 +index 0000000000000000000000000000000000000000..03d16da38f36b3c2e21c8c16758fe219c1d2f597 +--- /dev/null ++++ b/src/main/java/org/sucraft/suki/configuration/SukiWorldConfiguration.java +@@ -0,0 +1,60 @@ ++// Suki - Suki configuration ++ ++package org.sucraft.suki.configuration; ++ ++import com.mojang.logging.LogUtils; ++import io.papermc.paper.configuration.Configuration; ++import io.papermc.paper.configuration.ConfigurationPart; ++import io.papermc.paper.configuration.PaperConfigurations; ++import net.minecraft.resources.ResourceLocation; ++import org.slf4j.Logger; ++import org.spigotmc.SpigotWorldConfig; ++import org.spongepowered.configurate.objectmapping.meta.Setting; ++ ++@SuppressWarnings({"FieldCanBeLocal", "FieldMayBeFinal", "NotNullFieldNotInitialized", "InnerClassMayBeStatic"}) ++public class SukiWorldConfiguration extends ConfigurationPart { ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ public static final int CURRENT_VERSION = 19; ++ ++ private transient final SpigotWorldConfig spigotConfig; ++ private transient final ResourceLocation worldKey; ++ public SukiWorldConfiguration(SpigotWorldConfig spigotConfig, ResourceLocation worldKey) { ++ this.spigotConfig = spigotConfig; ++ this.worldKey = worldKey; ++ } ++ ++ public boolean isDefault() { ++ return this.worldKey.equals(PaperConfigurations.WORLD_DEFAULTS_KEY); ++ } ++ ++ @Setting(Configuration.VERSION_FIELD) ++ public int version = CURRENT_VERSION; ++ ++ public WorldDummyPart worldDummyPart; ++ ++ public class WorldDummyPart extends ConfigurationPart { ++ ++ int worldDummyValue = 0; ++ ++ } ++ // Suki start - only refresh lootables for players ++ ++ public Lootables lootables; ++ ++ public class Lootables extends ConfigurationPart { ++ public boolean onlyRefreshForPlayers = false; ++ } ++ ++ // Suki end - only refresh lootables for players ++ ++ // Suki start - do not relocate corrupted chunks ++ ++ public RelocateCorruptedChunks relocateCorruptedChunks; ++ ++ public class RelocateCorruptedChunks extends ConfigurationPart { ++ public boolean enabled = true; ++ } ++ ++ // Suki end - do not relocated corrupted chunks ++ ++}