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 2d3068b7748032342edd81e6ea4a7c08988fb28d..300b76731e1226b670cc51dfacf8e1cce4f04f7c 100644 --- a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java +++ b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java @@ -135,7 +135,7 @@ public class PaperConfigurations extends Configurations> 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) { @@ -303,7 +303,7 @@ public class PaperConfigurations extends Configurations public return createWorldContextMap(level.convertable.levelDirectory.path(), level.serverLevelData.getLevelName(), level.dimension().location(), level.spigotConfig); } @@ -402,18 +402,7 @@ public class PaperConfigurations extends Configurations public if (!Files.isDirectory(path)) { Files.createDirectories(path); } } + + // Plazma start + 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 77b40f3d7162befe4da29a60b3de2b93b261412c..0acbaa18a61d1cfdb894c6c756d2b9bdc3599b82 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -306,6 +306,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop public + // Plazma start + @Override + public org.plazmamc.plazma.configurations.PlazmaConfigurations plazmaConfigurations() { + return java.util.Objects.requireNonNull(this.plazmaConfigurations); + } + // Plazma end + public static Services create(YggdrasilAuthenticationService authenticationService, File rootDirectory, File userCacheFile, joptsimple.OptionSet optionSet) throws Exception { // Paper MinecraftSessionService minecraftSessionService = authenticationService.createMinecraftSessionService(); GameProfileRepository gameProfileRepository = authenticationService.createProfileRepository(); @@ -32,7 +39,11 @@ public record Services(MinecraftSessionService sessionService, ServicesKeySet se 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, authenticationService.getServicesKeySet(), 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, authenticationService.getServicesKeySet(), 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 2a70abb9e0af502885593df1e732887cd9d2ce4d..e6f14bb0825e3b6987ebb2ec782c8316e3b7c094 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); // Plazma 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 221d1d0e1b4b46de6ebca5faac09bbda875fae17..e009dba395779e2c89a36fe4524e5d40474a29e6 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java @@ -650,7 +650,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 c80d73035b05bfb6b3639175c51bb37c477c28d1..6d513c903e06fe79dffdffd02871d00cb958b715 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java @@ -174,7 +174,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 @@ -328,9 +333,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 875cc2357d17d37d8df921a84cf8af7b99d860f2..c43023fecf0882101248f625a28975586da05071 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -1051,6 +1051,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 @@ -2984,6 +2985,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 4bc0a370c7aec06d30b4ebf7fa7d73263d0543bc..206cec149875137836437bd237c2ff52baad4f0e 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..481392f3ed3b746172dbf319a583ca5751bc0b35 --- /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-defaults.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 52a6f1791c7de062d5d567d7cc9ee68731fd6e67..36ad8262b20cdd5ce4e94866c5d22cbdb5d5b385 100644 --- a/src/test/java/org/bukkit/support/AbstractTestingBase.java +++ b/src/test/java/org/bukkit/support/AbstractTestingBase.java @@ -64,6 +64,7 @@ public abstract class AbstractTestingBase { DummyEnchantments.setup(); io.papermc.paper.configuration.GlobalConfigTestingBase.setupGlobalConfigForTest(); // Paper + org.plazmamc.plazma.configurations.GlobalConfigurationTestingBase.setupGlobalConfigurationForTest(); // Plazma CraftRegistry.setMinecraftRegistry(REGISTRY_CUSTOM); 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); + } + } + } +}