From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: AlphaKR93 Date: Wed, 11 Jan 2023 02:24:51 +0900 Subject: [PATCH] Plazma Configurations diff --git a/src/main/java/io/papermc/paper/configuration/ConfigurationPart.java b/src/main/java/io/papermc/paper/configuration/ConfigurationPart.java index 7a4a7a654fe2516ed894a68f2657344df9d70f4c..ae51ab3c895b1b98d768e52b7c446bd6a3e89b9f 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 { // Plazma - package -> public 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 9ef6712c70fcd8912a79f3f61e351aac09572cf3..b7f44c74089058f261163430762f027aa9a5623a 100644 --- a/src/main/java/io/papermc/paper/configuration/Configurations.java +++ b/src/main/java/io/papermc/paper/configuration/Configurations.java @@ -88,7 +88,7 @@ public abstract class Configurations { }; } - static CheckedFunction reloader(Class type, T instance) { + public static CheckedFunction reloader(Class type, T instance) { // Plazma - package -> public return node -> { ObjectMapper.Factory factory = (ObjectMapper.Factory) Objects.requireNonNull(node.options().serializers().get(type)); ObjectMapper.Mutable mutable = (ObjectMapper.Mutable) factory.get(type); @@ -148,7 +148,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()); // Plazma } this.applyWorldConfigTransformations(contextMap, node); final W instance = node.require(this.worldConfigClass); @@ -207,7 +207,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()); // Plazma } this.applyWorldConfigTransformations(contextMap, worldNode); this.applyDefaultsAwareWorldConfigTransformations(contextMap, worldNode, defaultsNode); @@ -232,6 +232,21 @@ public abstract class Configurations { return level.convertable.levelDirectory.path().resolve(this.worldConfigFileName); } + // Plazma start + @Deprecated + public org.bukkit.configuration.file.YamlConfiguration createLegacyObject(final net.minecraft.server.MinecraftServer server) { + org.bukkit.configuration.file.YamlConfiguration global = org.bukkit.configuration.file.YamlConfiguration.loadConfiguration(this.globalFolder.resolve(this.globalConfigFileName).toFile()); + org.bukkit.configuration.ConfigurationSection worlds = global.createSection("__________WORLDS__________"); + worlds.set("__defaults__", org.bukkit.configuration.file.YamlConfiguration.loadConfiguration(this.globalFolder.resolve(this.defaultWorldConfigFileName).toFile())); + for (ServerLevel level : server.getAllLevels()) { + worlds.set(level.getWorld().getName(), org.bukkit.configuration.file.YamlConfiguration.loadConfiguration(getWorldConfigFile(level).toFile())); + } + return global; + } + + protected abstract int getWorldConfigurationCurrentVersion(); + // Plazma end + public static class ContextMap { private static final Object VOID = new Object(); diff --git a/src/main/java/io/papermc/paper/configuration/InnerClassFieldDiscoverer.java b/src/main/java/io/papermc/paper/configuration/InnerClassFieldDiscoverer.java index a0aa1f1a7adf986d500a2135aa42e138aa3c4f08..28a1d21900dbff4b9d1887b9aa4e68f4703b778f 100644 --- a/src/main/java/io/papermc/paper/configuration/InnerClassFieldDiscoverer.java +++ b/src/main/java/io/papermc/paper/configuration/InnerClassFieldDiscoverer.java @@ -17,7 +17,7 @@ import java.util.Map; import static io.leangen.geantyref.GenericTypeReflector.erase; -final class InnerClassFieldDiscoverer implements FieldDiscoverer> { +public final class InnerClassFieldDiscoverer implements FieldDiscoverer> { // Plazma - package -> public private final Map, Object> instanceMap = new HashMap<>(); private final Map, Object> overrides; @@ -136,7 +136,19 @@ final class InnerClassFieldDiscoverer implements FieldDiscoverer globalConfig() { + public static FieldDiscoverer globalConfig() { // Plazma - package -> public return new InnerClassFieldDiscoverer(Collections.emptyMap()); } + + // Plazma start + public static FieldDiscoverer plazmaLevelConfiguration(Configurations.ContextMap contextMap) { + final Map, Object> overrides = Map.of( + org.plazmamc.plazma.configurations.LevelConfigurations.class, + new org.plazmamc.plazma.configurations.LevelConfigurations( + contextMap.require(Configurations.WORLD_KEY) + ) + ); + return new InnerClassFieldDiscoverer(overrides); + } + // Plazma end } diff --git a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java index f6b9d216c24d8858802f85209fe1a869e5a9be31..746fdd880862e7dd8b53dec99b07ae627764dcf8 100644 --- a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java +++ b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java @@ -127,13 +127,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)) { // Plazma - private -> public @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"); // Plazma - package -> public public PaperConfigurations(final Path globalFolder) { @@ -297,7 +297,7 @@ public class PaperConfigurations extends Configurations public return createWorldContextMap(level.convertable.levelDirectory.path(), level.serverLevelData.getLevelName(), level.dimension().location(), level.spigotConfig); } @@ -398,17 +398,6 @@ public class PaperConfigurations extends Configurations public if (!Files.isDirectory(path)) { Files.createDirectories(path); } } + + // Plazma start + @Override + protected int getWorldConfigurationCurrentVersion() { + return WorldConfiguration.CURRENT_VERSION; + } + // Plazma end } diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 615c456de68a20d0e95b30e124a2bdf46039408f..cc6968a064b30f7250d18c20efb2bb8602bb0cdd 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -309,6 +309,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 +38,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); + // Plazma start + final java.nio.file.Path plazmaConfigurationDirPath = ((File) optionSet.valueOf("plazma-configurations-directory")).toPath(); + org.plazmamc.plazma.configurations.PlazmaConfigurations plazmaConfigurations = org.plazmamc.plazma.configurations.PlazmaConfigurations.setup(plazmaConfigurationDirPath); + return new Services(minecraftSessionService, signatureValidator, gameProfileRepository, gameProfileCache, paperConfigurations, plazmaConfigurations); + // Plazma end // 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 6ecc75621867390738e804e06ac284524664473d..b91910eaac0e9c18722e52d223a4420199d77a7f 100644 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java @@ -209,6 +209,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(); + // Plazma start + plazmaConfigurations.initializeGlobalConfiguration(); + plazmaConfigurations.initializeWorldDefaultsConfiguration(); + // Plazma end // Paper start - moved up to right after PlayerList creation but before file load/save if (this.convertOldUsers()) { this.getProfileCache().save(false); // Paper @@ -218,6 +222,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface org.spigotmc.WatchdogThread.doStart(org.spigotmc.SpigotConfig.timeoutTime, org.spigotmc.SpigotConfig.restartOnCrash); thread.start(); // Paper - start console thread after MinecraftServer.console & PaperConfig are initialized io.papermc.paper.command.PaperCommands.registerCommands(this); + org.plazmamc.plazma.commands.PlazmaCommands.registerCommands(this); com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Purpur start try { diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java index 86b8485c0fb1dc5cd79c9e24546dc74459822a48..ebbf462835e5a14fe1413c724d4985635207a897 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java @@ -530,7 +530,7 @@ public class ServerLevel extends Level implements WorldGenLevel { // Holder holder = worlddimension.type(); // CraftBukkit - decompile error // Objects.requireNonNull(minecraftserver); // CraftBukkit - decompile error - super(iworlddataserver, resourcekey, minecraftserver.registryAccess(), worlddimension.type(), 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, minecraftserver.registryAccess(), worlddimension.type(), 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.plazmaConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig)), executor); // Paper - Async-Anti-Xray - Pass executor // Plazma 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/Level.java b/src/main/java/net/minecraft/world/level/Level.java index 92b440b24c6b083f81837611d08fbd6773a2a6e6..058449f24eb3260dc230dad2a0b4c552d0b7f40e 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java @@ -175,7 +175,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable { return this.paperConfig; } // Paper end - + // Plazma start + private final org.plazmamc.plazma.configurations.LevelConfigurations plazmaLevelConfiguration; + public org.plazmamc.plazma.configurations.LevelConfigurations plazmaLevelConfiguration() { + return this.plazmaLevelConfiguration; + } + // Plazma end public final com.destroystokyo.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray public final org.purpurmc.purpur.PurpurWorldConfig purpurConfig; // Purpur public final co.aikar.timings.WorldTimingsHandler timings; // Paper @@ -329,9 +334,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable { @Override public final int getHeight() { return this.height; } // Pufferfish end - protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, RegistryAccess iregistrycustom, 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, RegistryAccess iregistrycustom, 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 plazmaLevelConfigurationCreator, java.util.concurrent.Executor executor) { // Paper - Async-Anti-Xray - Pass executor this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper + this.plazmaLevelConfiguration = plazmaLevelConfigurationCreator.apply(this.spigotConfig); // Plazma this.purpurConfig = new org.purpurmc.purpur.PurpurWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName(), env); // Purpur this.playerBreedingCooldowns = this.getNewBreedingCooldownCache(); // Purpur this.generator = gen; diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java index 5e6952a9d4c1137dc3d720ee2c944d95d4628065..1367072684078c0e93c275c2159ece799f96428b 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -993,6 +993,7 @@ public final class CraftServer implements Server { org.spigotmc.SpigotConfig.init((File) console.options.valueOf("spigot-settings")); // Spigot this.console.paperConfigurations.reloadConfigs(this.console); + this.console.plazmaConfigurations.reloadConfigurations(this.console); // Plazma org.purpurmc.purpur.PurpurConfig.init((File) console.options.valueOf("purpur-settings")); // Purpur for (ServerLevel world : this.console.getAllLevels()) { // world.serverLevelData.setDifficulty(config.difficulty); // Paper - per level difficulty @@ -2835,6 +2836,13 @@ public final class CraftServer implements Server { return CraftServer.this.console.paperConfigurations.createLegacyObject(CraftServer.this.console); } + // Plazma start + @Override + public YamlConfiguration getPlazmaConfiguration() { + return CraftServer.this.console.plazmaConfigurations.createLegacyObject(CraftServer.this.console); + } + // Plazma end + // Purpur start @Override public YamlConfiguration getPurpurConfig() { diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java index 288cf98287c6d3c073b9ab6696c3957c999cad32..4970b246356bbd04eb1f9715da3fc7c9494573e2 100644 --- a/src/main/java/org/bukkit/craftbukkit/Main.java +++ b/src/main/java/org/bukkit/craftbukkit/Main.java @@ -173,6 +173,14 @@ public class Main { .describedAs("Jar file"); // Paper end + // Plazma start + acceptsAll(asList("plazma-dir", "plazma-configurations-directory"), "Directory for Plazma configurations") + .withRequiredArg() + .ofType(File.class) + .defaultsTo(new File(io.papermc.paper.configuration.PaperConfigurations.CONFIG_DIR)) + .describedAs("Config directory"); + // Plazma end + // Purpur Start acceptsAll(asList("purpur", "purpur-settings"), "File for purpur settings") .withRequiredArg() diff --git a/src/main/java/org/plazmamc/plazma/commands/PlazmaCommand.java b/src/main/java/org/plazmamc/plazma/commands/PlazmaCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..9fb819b3745ec98d81a53650d9095ded4e9aa914 --- /dev/null +++ b/src/main/java/org/plazmamc/plazma/commands/PlazmaCommand.java @@ -0,0 +1,127 @@ +package org.plazmamc.plazma.commands; + +import io.papermc.paper.command.CommandUtil; +import it.unimi.dsi.fastutil.Pair; +import net.kyori.adventure.text.format.NamedTextColor; +import net.minecraft.Util; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionDefault; +import org.bukkit.plugin.PluginManager; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.DefaultQualifier; +import org.plazmamc.plazma.commands.subcommands.PlazmaSubCommand; +import org.plazmamc.plazma.commands.subcommands.ReloadCommand; +import org.plazmamc.plazma.commands.subcommands.VersionCommand; + +import java.util.*; +import java.util.stream.Collectors; + +import static net.kyori.adventure.text.Component.text; + +@DefaultQualifier(NonNull.class) +public class PlazmaCommand extends Command { + + private static final Map SUB_COMMANDS = Util.make(() -> { + final Map, PlazmaSubCommand> commands = new HashMap<>(); + + commands.put(Set.of("reload"), new ReloadCommand()); + commands.put(Set.of("version"), new VersionCommand()); + + return commands.entrySet().stream() + .flatMap(entry -> entry.getKey().stream().map(key -> Map.entry(key, entry.getValue()))) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + }); + + private static final Map ALIASES = Util.make(() -> { + final Map> aliases = new HashMap<>(); + + aliases.put("reload", Set.of("rl")); + aliases.put("version", Set.of("ver")); + + return aliases.entrySet().stream() + .flatMap(entry -> entry.getValue().stream().map(s -> Map.entry(s, entry.getKey()))) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + }); + + public PlazmaCommand(final String name) { + super(name); + + final PluginManager pluginManager = Bukkit.getServer().getPluginManager(); + + final List permissions = new ArrayList<>(); + permissions.add("bukkit.command.plazma"); + permissions.addAll(SUB_COMMANDS.keySet().stream().map(s -> "bukkit.command.plazma." + s).toList()); + + this.description = "Plazma related commands"; + this.usageMessage = String.format("/plazma [%s]", String.join("|", SUB_COMMANDS.keySet())); + this.setPermission(String.join(";", permissions)); + + for (final String perm : permissions) + pluginManager.addPermission(new Permission(perm, PermissionDefault.OP)); + } + + @Override + public List tabComplete(final CommandSender sender, final String aliases, final String[] args) throws IllegalArgumentException { + if (args.length <= 1) + return CommandUtil.getListMatchingLast(sender, args, SUB_COMMANDS.keySet()); + + final @Nullable Pair subCommand = resolveSubCommand(args[0]); + if (subCommand != null) + return subCommand.second().tabComplete(sender, subCommand.first(), Arrays.copyOfRange(args, 1, args.length)); + + return Collections.emptyList(); + } + + @Override + public boolean execute(final CommandSender sender, final String commandLabel, final String[] args) { + if (!testPermission(sender)) return true; + + if (args.length == 0) { + sender.sendMessage(text("Usage: " + this.usageMessage, NamedTextColor.RED)); + return false; + } + + final @Nullable Pair subCommand = resolveSubCommand(args[0]); + + if (subCommand == null) { + sender.sendMessage(text("Usage: " + this.usageMessage, NamedTextColor.RED)); + return false; + } + + if (!testPermission(sender, subCommand.first())) return true; + + final String[] choppedArgs = Arrays.copyOfRange(args, 1, args.length); + return subCommand.second().execute(sender, subCommand.first(), choppedArgs); + } + + private static @Nullable Pair resolveSubCommand(String label) { + label = label.toLowerCase(Locale.ENGLISH); + @Nullable PlazmaSubCommand subCommand = SUB_COMMANDS.get(label); + + if (subCommand == null) { + final @Nullable String command = ALIASES.get(label); + if (command != null) { + label = command; + subCommand = SUB_COMMANDS.get(command); + } + } + + if (subCommand != null) + return Pair.of(label, subCommand); + + return null; + } + + private static boolean testPermission(final CommandSender sender, final String permission) { + if (sender.hasPermission("bukkit.command.plazma." + permission) || sender.hasPermission("bukkit.command.plazma")) + return true; + + sender.sendMessage(Bukkit.permissionMessage()); + return false; + } +} diff --git a/src/main/java/org/plazmamc/plazma/commands/PlazmaCommands.java b/src/main/java/org/plazmamc/plazma/commands/PlazmaCommands.java new file mode 100644 index 0000000000000000000000000000000000000000..4223faf9cdeb76cbafbb9b5b402ca3293c2afc1d --- /dev/null +++ b/src/main/java/org/plazmamc/plazma/commands/PlazmaCommands.java @@ -0,0 +1,25 @@ +package org.plazmamc.plazma.commands; + +import net.minecraft.server.MinecraftServer; +import org.bukkit.command.Command; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; + +import java.util.HashMap; +import java.util.Map; + +@DefaultQualifier(NonNull.class) +public final class PlazmaCommands { + + private PlazmaCommands() {} + + private static final Map COMMANDS = new HashMap<>(); + static { + COMMANDS.put("plazma", new PlazmaCommand("plazma")); + } + + public static void registerCommands(final MinecraftServer server) { + COMMANDS.forEach((s, command) -> server.server.getCommandMap().register(s, "Plazma", command)); + } + +} diff --git a/src/main/java/org/plazmamc/plazma/commands/subcommands/PlazmaSubCommand.java b/src/main/java/org/plazmamc/plazma/commands/subcommands/PlazmaSubCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..32f2f9c933069c32c321f71ec2c57e2261f7ad1e --- /dev/null +++ b/src/main/java/org/plazmamc/plazma/commands/subcommands/PlazmaSubCommand.java @@ -0,0 +1,17 @@ +package org.plazmamc.plazma.commands.subcommands; + +import org.bukkit.command.CommandSender; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; + +import java.util.Collections; +import java.util.List; + +@DefaultQualifier(NonNull.class) +public interface PlazmaSubCommand { + boolean execute(CommandSender sender, String subCommand, String[] args); + + default List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { + return Collections.emptyList(); + } +} diff --git a/src/main/java/org/plazmamc/plazma/commands/subcommands/ReloadCommand.java b/src/main/java/org/plazmamc/plazma/commands/subcommands/ReloadCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..9b1964bf2c05e71cac633fd75f887a5e516623df --- /dev/null +++ b/src/main/java/org/plazmamc/plazma/commands/subcommands/ReloadCommand.java @@ -0,0 +1,29 @@ +package org.plazmamc.plazma.commands.subcommands; + +import net.kyori.adventure.text.format.NamedTextColor; +import net.minecraft.server.MinecraftServer; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.craftbukkit.CraftServer; + +import static net.kyori.adventure.text.Component.text; + +public class ReloadCommand implements PlazmaSubCommand { + + @Override + public boolean execute(CommandSender sender, String subCommand, String[] args) { + this.doReload(sender); + return true; + } + + private void doReload(final CommandSender sender) { + Command.broadcastCommandMessage(sender, text("Please note that this command is not supported and may cause issues.", NamedTextColor.RED)); + Command.broadcastCommandMessage(sender, text("If you encounter any issues please use the /stop command to restart your server.", NamedTextColor.RED)); + + MinecraftServer server = ((CraftServer) sender.getServer()).getServer(); + server.plazmaConfigurations.reloadConfigurations(server); + server.server.reloadCount++; + + Command.broadcastCommandMessage(sender, text("Successfully reloaded Plazma configuration files.", NamedTextColor.GREEN)); + } +} diff --git a/src/main/java/org/plazmamc/plazma/commands/subcommands/VersionCommand.java b/src/main/java/org/plazmamc/plazma/commands/subcommands/VersionCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..85002144e0b350c4ae044e1a4a4c1734cc27c059 --- /dev/null +++ b/src/main/java/org/plazmamc/plazma/commands/subcommands/VersionCommand.java @@ -0,0 +1,15 @@ +package org.plazmamc.plazma.commands.subcommands; + +import net.minecraft.server.MinecraftServer; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class VersionCommand implements PlazmaSubCommand { + @Override + public boolean execute(CommandSender sender, String subCommand, String[] args) { + final @Nullable Command ver = MinecraftServer.getServer().server.getCommandMap().getCommand("version"); + if (ver != null) ver.execute(sender, "plazma", new String[0]); + return true; + } +} diff --git a/src/main/java/org/plazmamc/plazma/configurations/GlobalConfiguration.java b/src/main/java/org/plazmamc/plazma/configurations/GlobalConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..6714947455c1e4c887a5f13ba1a3aeb57324ee0a --- /dev/null +++ b/src/main/java/org/plazmamc/plazma/configurations/GlobalConfiguration.java @@ -0,0 +1,23 @@ +package org.plazmamc.plazma.configurations; + +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 GlobalConfiguration extends ConfigurationPart { + static final int CURRENT_VERSION = 1; + static final boolean DO_OPTIMIZE = !Boolean.getBoolean("Plazma.disableConfigOptimization"); + private static GlobalConfiguration instance; + + public static GlobalConfiguration get() { + return instance; + } + + static void set(GlobalConfiguration instance) { + GlobalConfiguration.instance = instance; + } + + @Setting(Configuration.VERSION_FIELD) + public int version = CURRENT_VERSION; +} diff --git a/src/main/java/org/plazmamc/plazma/configurations/LevelConfigurations.java b/src/main/java/org/plazmamc/plazma/configurations/LevelConfigurations.java new file mode 100644 index 0000000000000000000000000000000000000000..857ddc62dd9affbbebcd7cde8a6d675dbb5f68ae --- /dev/null +++ b/src/main/java/org/plazmamc/plazma/configurations/LevelConfigurations.java @@ -0,0 +1,25 @@ +package org.plazmamc.plazma.configurations; + +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.spongepowered.configurate.objectmapping.meta.Setting; + +@SuppressWarnings({"FieldCanBeLocal", "FieldMayBeFinal", "NotNullFieldNotInitialized", "InnerClassMayBeStatic"}) +public class LevelConfigurations extends ConfigurationPart { + public static final int CURRENT_VERSION = 1; + private static final boolean DO_OPTIMIZE = !Boolean.getBoolean("Plazma.disableConfigOptimization"); + + private transient final ResourceLocation worldKey; + public LevelConfigurations(ResourceLocation worldKey) { + this.worldKey = worldKey; + } + + public boolean isDefault() { + return this.worldKey.equals(PaperConfigurations.WORLD_DEFAULTS_KEY); + } + + @Setting(Configuration.VERSION_FIELD) + public int version = CURRENT_VERSION; +} diff --git a/src/main/java/org/plazmamc/plazma/configurations/PlazmaConfigurations.java b/src/main/java/org/plazmamc/plazma/configurations/PlazmaConfigurations.java new file mode 100644 index 0000000000000000000000000000000000000000..b1104778995a3734f3b6df71d59172e546e152a2 --- /dev/null +++ b/src/main/java/org/plazmamc/plazma/configurations/PlazmaConfigurations.java @@ -0,0 +1,304 @@ +package org.plazmamc.plazma.configurations; + +import com.google.common.collect.Table; +import com.mojang.logging.LogUtils; +import io.leangen.geantyref.TypeToken; +import io.papermc.paper.configuration.*; +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.PacketClassSerializer; +import io.papermc.paper.configuration.serializer.StringRepresentableSerializer; +import io.papermc.paper.configuration.serializer.collections.FastutilMapSerializer; +import io.papermc.paper.configuration.serializer.collections.MapSerializer; +import io.papermc.paper.configuration.serializer.collections.TableSerializer; +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.*; +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.registries.Registries; +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.jetbrains.annotations.VisibleForTesting; +import org.slf4j.Logger; +import org.spongepowered.configurate.*; +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 PlazmaConfigurations extends Configurations { + + private static final Logger LOGGER = LogUtils.getLogger(); + static final String GLOBAL_CONFIGURATION_FILE_NAME= "plazma-global.yml"; + static final String LEVEL_DEFAULT_CONFIGURATION_FILE_NAME = "plazma-level-dafaults.yml"; + static final String LEVEL_CONFIGURATION_FILE_NAME = "plazma-configuration.yml"; + + private static final String HEADER_START = """ + # English + This is the %s configuration file for Plazma. + 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 Plazma, + join us in our Discord for Plazma, or check the GitHub Wiki pages. + + %s + + # 한국어 + 본 파일은 Plazma의 %s 구성 파일입니다. + 보시다시피, 굉장히 많은 설정이 있습니다. 몇몇 설정은 게임플레이에 영향을 줄 수 있으므로, + 주의해서 사용하시기 바라며, 각 설정이 어떠한 역할을 하는지 알고 사용하시기 바랍니다. + + 만약 구성에 관한 도움이 필요하거나 Plazma에 관련한 질문이 있으시다면, + Discord 또는 네이버 카페에 가입하거나, GitHub 위키 페이지를 참고하시기 바랍니다. + + %s + + Wiki: https://github.com/PlazmaMC/Plazma/wiki + Discord: https://discord.gg/MmfC52K8A8 + Twitter: *COMMING SOON* + Naver Cafe: *COMMING SOON* + """; + + private static final Function LEVEL_SPECIFIC_HEADER = map -> String.format(""" + # English + This is a level specific configuration file for Plazma. + This file may start empty, but can be filled with settings to override ones in the %s/%s + + If you need help with the configuration or have any questions related to Plazma, + join us in our Discord for Plazma, or check the GitHub Wiki pages. + + + # 한국어 + 본 파일은 Plazma의 레벨별 구성 파일입니다. + 이 파일은 비어있을 수 있지만, %s/%s 파일의 설정을 덮어쓰기 위해 설정을 채울 수 있습니다. + + 만약 구성에 관한 도움이 필요하거나 Plazma에 관련한 질문이 있으시다면, + Discord 또는 네이버 카페에 가입하거나, GitHub 위키 페이지를 참고하시기 바랍니다. + + + Level: %s (%s) + + Wiki: https://github.com/PlazmaMC/Plazma/wiki + Discord: https://discord.gg/MmfC52K8A8 + Twitter: *COMMING SOON* + Naver Cafe: *COMMING SOON* + """, PaperConfigurations.CONFIG_DIR, LEVEL_DEFAULT_CONFIGURATION_FILE_NAME, PaperConfigurations.CONFIG_DIR, LEVEL_DEFAULT_CONFIGURATION_FILE_NAME, + map.require(WORLD_NAME), map.require(WORLD_KEY)); + + private static final String GLOBAL_HEADER = String.format(HEADER_START, + "global", String.format(""" + The level configuration options are inside their respective level folder. + The files are named %s + """, LEVEL_CONFIGURATION_FILE_NAME), + "전역", String.format(""" + 레벨 구성 옵션은 각각의 레벨 폴더 안에 있으며, 파일 이름은 %s 입니다. + """, LEVEL_CONFIGURATION_FILE_NAME) + ); + + private static final String LEVEL_DEFAULTS_HEADER = String.format(HEADER_START, + "level defaults", """ + Configuration options here apply to all levels, unless you specify overrides inside + the level-specific config file inside each level folder. + """, + "기본 레벨", """ + 이 구성 파일의 설정은 각 레벨 폴더 안 구성 파일에서 덮어쓰기 되지 않는 한 모든 레벨에 적용됩니다. + """ + ); + + private static final List DEFAULTS_AWARE_TRANSFORMATIONS = Collections.emptyList(); + + public PlazmaConfigurations(final Path globalFolder) { + super(globalFolder, GlobalConfiguration.class, LevelConfigurations.class, GLOBAL_CONFIGURATION_FILE_NAME, LEVEL_DEFAULT_CONFIGURATION_FILE_NAME, LEVEL_CONFIGURATION_FILE_NAME); + } + + // Create Loader Builder + 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 YamlConfigurationLoader.Builder createLoaderBuilder() { + return super.createLoaderBuilder().defaultOptions(PlazmaConfigurations::defaultOptions); + } + + // Create Global Object Mapper Factory Builder + private static ObjectMapper.Factory.Builder defaultGlobalFactoryBuilder(ObjectMapper.Factory.Builder builder) { + return builder.addDiscoverer(InnerClassFieldDiscoverer.globalConfig()); + } + + @Override + protected ObjectMapper.Factory.Builder createGlobalObjectMapperFactoryBuilder() { + return defaultGlobalFactoryBuilder(super.createGlobalObjectMapperFactoryBuilder()); + } + + // Create Global Loader Builder + private static ConfigurationOptions defaultGlobalOptions(ConfigurationOptions options) { + return options.header(GLOBAL_HEADER).serializers(builder -> builder.register(new PacketClassSerializer())); + } + + @Override + protected YamlConfigurationLoader.Builder createGlobalLoaderBuilder() { + return super.createGlobalLoaderBuilder().defaultOptions(PlazmaConfigurations::defaultGlobalOptions); + } + + // Initialize + @Override + public GlobalConfiguration initializeGlobalConfiguration() throws ConfigurateException { + GlobalConfiguration configuration = super.initializeGlobalConfiguration(); + GlobalConfiguration.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.plazmaLevelConfiguration(contextMap)); + } + + @Override + protected YamlConfigurationLoader.Builder createWorldConfigLoaderBuilder(final ContextMap contextMap) { + return super.createWorldConfigLoaderBuilder(contextMap).defaultOptions(options -> options + .header(contextMap.require(WORLD_NAME).equals(WORLD_DEFAULTS) ? LEVEL_DEFAULTS_HEADER : LEVEL_SPECIFIC_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>() {}, Registries.ENTITY_TYPE, true)) + .register(new RegistryValueSerializer<>(Item.class, Registries.ITEM, true)) + .register(new RegistryHolderSerializer<>(new TypeToken>() {}, Registries.CONFIGURED_FEATURE, false)) + .register(new RegistryHolderSerializer<>(Item.class, Registries.ITEM, true)) + ) + ); + } + + private void applyTransformations(final NodePath[] paths, final ConfigurationNode node) throws ConfigurateException { + if (paths.length > 0) { + ConfigurationTransformation.Builder builder = ConfigurationTransformation.builder(); + + for (NodePath path : paths) + builder.addAction(path, TransformAction.remove()); + + builder.build().apply(node); + } + } + + @Override + protected void applyGlobalConfigTransformations(final ConfigurationNode node) throws ConfigurateException { + applyTransformations(RemovedConfigurations.REMOVED_GLOBAL_PATHS, node); + } + + @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 Plazma level configuration file for {} didn't have a version field, assuming latest", world); + version.raw(LevelConfigurations.CURRENT_VERSION); + } + + applyTransformations(RemovedConfigurations.REMOVED_LEVEL_PATHS, node); + } + + @Override + protected void applyDefaultsAwareWorldConfigTransformations(final ContextMap contextMap, final ConfigurationNode levelNode, final ConfigurationNode defaultsNode) throws ConfigurateException { + final ConfigurationTransformation.Builder builder = ConfigurationTransformation.builder(); + DEFAULTS_AWARE_TRANSFORMATIONS.forEach(transform -> transform.apply(builder, contextMap, defaultsNode)); + + ConfigurationTransformation transformation; + try { + transformation = builder.build(); + } catch (IllegalArgumentException ignored) { + return; + } + transformation.apply(levelNode); + } + + @Override + public LevelConfigurations createWorldConfig(final ContextMap contextMap) { + final String levelName = contextMap.require(WORLD_NAME); + try { + return super.createWorldConfig(contextMap); + } catch (IOException exception) { + throw new RuntimeException(String.format("Could not create Plazma level configuration for %s", levelName), exception); + } + } + + @Override + protected boolean isConfigType(Type type) { + return ConfigurationPart.class.isAssignableFrom(erase(type)); + } + + @Override + protected int getWorldConfigurationCurrentVersion() { + return LevelConfigurations.CURRENT_VERSION; + } + + @VisibleForTesting + static ConfigurationNode createForTesting() { + ObjectMapper.Factory factory = defaultGlobalFactoryBuilder(ObjectMapper.factoryBuilder()).build(); + ConfigurationOptions options = defaultGlobalOptions(defaultOptions(ConfigurationOptions.defaults())) + .serializers(builder -> builder.register(type -> ConfigurationPart.class.isAssignableFrom(erase(type)), factory.asTypeSerializer())); + return BasicConfigurationNode.root(options); + } + + public static PlazmaConfigurations setup(final Path configurationDir) throws Exception { + try { + PaperConfigurations.createDirectoriesSymlinkAware(configurationDir); + return new PlazmaConfigurations(configurationDir); + } catch (final IOException e) { + throw new RuntimeException("Could not setup Plazma configuration files", e); + } + } + + public void reloadConfigurations(MinecraftServer server) { + try { + this.initializeGlobalConfiguration(reloader(this.globalConfigClass, GlobalConfiguration.get())); + this.initializeWorldDefaultsConfiguration(); + for (ServerLevel level : server.getAllLevels()) + this.createWorldConfig(PaperConfigurations.createWorldContextMap(level), reloader(this.worldConfigClass, level.plazmaLevelConfiguration())); + } catch (Exception e) { + throw new RuntimeException("Could not reload Plazma configuration files", e); + } + } +} diff --git a/src/main/java/org/plazmamc/plazma/configurations/RemovedConfigurations.java b/src/main/java/org/plazmamc/plazma/configurations/RemovedConfigurations.java new file mode 100644 index 0000000000000000000000000000000000000000..469100cd86e6742eeebad22923097782dc86bf37 --- /dev/null +++ b/src/main/java/org/plazmamc/plazma/configurations/RemovedConfigurations.java @@ -0,0 +1,11 @@ +package org.plazmamc.plazma.configurations; + +import org.spongepowered.configurate.NodePath; + +interface RemovedConfigurations { + + NodePath[] REMOVED_GLOBAL_PATHS = {}; + + NodePath[] REMOVED_LEVEL_PATHS = {}; + +} diff --git a/src/test/java/org/bukkit/support/AbstractTestingBase.java b/src/test/java/org/bukkit/support/AbstractTestingBase.java index a616624a7beb35239be0fc2bb7fe60db1c673c2d..602ac1b30937a89312be7ba068a595d398e53394 100644 --- a/src/test/java/org/bukkit/support/AbstractTestingBase.java +++ b/src/test/java/org/bukkit/support/AbstractTestingBase.java @@ -63,6 +63,7 @@ public abstract class AbstractTestingBase { DummyEnchantments.setup(); io.papermc.paper.configuration.GlobalConfigTestingBase.setupGlobalConfigForTest(); // Paper + org.plazmamc.plazma.configurations.GlobalConfigurationTestingBase.setupGlobalConfigurationForTest(); // Plazma ImmutableList.Builder builder = ImmutableList.builder(); for (Material m : Material.values()) { diff --git a/src/test/java/org/plazmamc/plazma/configurations/GlobalConfigurationTestingBase.java b/src/test/java/org/plazmamc/plazma/configurations/GlobalConfigurationTestingBase.java new file mode 100644 index 0000000000000000000000000000000000000000..2b9a17a2e04f7fb6b801920ed04133db9478f984 --- /dev/null +++ b/src/test/java/org/plazmamc/plazma/configurations/GlobalConfigurationTestingBase.java @@ -0,0 +1,18 @@ +package org.plazmamc.plazma.configurations; + +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.serialize.SerializationException; + +public class GlobalConfigurationTestingBase { + public static void setupGlobalConfigurationForTest() { + if (GlobalConfiguration.get() == null) { + ConfigurationNode node = PlazmaConfigurations.createForTesting(); + try { + GlobalConfiguration globalConfiguration = node.require(GlobalConfiguration.class); + GlobalConfiguration.set(globalConfiguration); + } catch (SerializationException e) { + throw new RuntimeException(e); + } + } + } +}