From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: "kfian294ma4@gmail.com" Date: Sun, 5 Sep 2021 18:01:34 +0100 Subject: [PATCH] Sakura Configuration Files diff --git a/src/main/java/io/papermc/paper/configuration/Configurations.java b/src/main/java/io/papermc/paper/configuration/Configurations.java index 87e5f614ba988547a827486740db217e28585773..0375c9eb296ba7e8ed64e8d00dbc13a581dd4f90 100644 --- a/src/main/java/io/papermc/paper/configuration/Configurations.java +++ b/src/main/java/io/papermc/paper/configuration/Configurations.java @@ -96,7 +96,7 @@ public abstract class Configurations { }; } - static CheckedFunction reloader(Class type, T instance) { + public static CheckedFunction reloader(Class type, T instance) { // Sakura - package-protected -> public return node -> { ObjectMapper.Factory factory = (ObjectMapper.Factory) Objects.requireNonNull(node.options().serializers().get(type)); ObjectMapper.Mutable mutable = (ObjectMapper.Mutable) factory.get(type); @@ -231,7 +231,7 @@ public abstract class Configurations { .path(worldConfigFile) .build(); final ConfigurationNode worldNode = worldLoader.load(); - if (newFile) { // set the version field if new file + if (newFile && this instanceof PaperConfigurations) { // Sakura - hack this into working // set the version field if new file worldNode.node(Configuration.VERSION_FIELD).set(this.worldConfigVersion()); } else { this.verifyWorldConfigVersion(contextMap, worldNode); diff --git a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java index 83a726bcf8b7dce73a361b0d79dbd63a0afc7a12..6efe0ed8600e3703bd83d46545bba88791d3bcd6 100644 --- a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java +++ b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java @@ -457,7 +457,7 @@ public class PaperConfigurations extends Configurations public if (!Files.isDirectory(path)) { Files.createDirectories(path); } diff --git a/src/main/java/me/samsuik/sakura/command/BaseSubCommand.java b/src/main/java/me/samsuik/sakura/command/BaseSubCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..87d8fd1fe60dfea4ce697048a34d9bb888cb6d4b --- /dev/null +++ b/src/main/java/me/samsuik/sakura/command/BaseSubCommand.java @@ -0,0 +1,45 @@ +package me.samsuik.sakura.command; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +@DefaultQualifier(NonNull.class) +public abstract class BaseSubCommand extends Command { + public BaseSubCommand(String name) { + super(name); + this.description = "Sakura Command " + name; + this.setPermission("bukkit.command." + name); + } + + public abstract void execute(CommandSender sender, String[] args); + + public void tabComplete(List list, String[] args) throws IllegalArgumentException {} + + @Override + @Deprecated + public final boolean execute(CommandSender sender, String label, String[] args) { + if (this.testPermission(sender)) { + this.execute(sender, args); + } + + return true; + } + + @Override + @NotNull + public List tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException { + List completions = new ArrayList<>(0); + + if (this.testPermissionSilent(sender)) { + this.tabComplete(completions, args); + } + + return completions; + } +} diff --git a/src/main/java/me/samsuik/sakura/command/SakuraCommand.java b/src/main/java/me/samsuik/sakura/command/SakuraCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..18c74437018ff4c0935242f6002083edb1282a01 --- /dev/null +++ b/src/main/java/me/samsuik/sakura/command/SakuraCommand.java @@ -0,0 +1,86 @@ +package me.samsuik.sakura.command; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import net.minecraft.server.MinecraftServer; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; + +@DefaultQualifier(NonNull.class) +public final class SakuraCommand extends Command { + private static final Component INFORMATION_MESSAGE = MiniMessage.miniMessage().deserialize(""" + . + | This is the main command for Sakura. + | All exclusive commands are listed below.""" + ); + + private static final String COMMAND_MSG = "| * /"; + + public SakuraCommand(String name) { + super(name); + this.description = ""; + this.usageMessage = "/sakura"; + this.setPermission("bukkit.command.sakura"); + } + + @Override + public boolean execute(CommandSender sender, String commandLabel, String[] args) { + if (args.length > 0) { + List commands = new ArrayList<>(SakuraCommands.COMMANDS.values()); + + // This part is copied from the VersionCommand SubCommand in paper + Command internalVersion = MinecraftServer.getServer().server.getCommandMap().getCommand("version"); + if (internalVersion != null) { + commands.add(internalVersion); + } + + for (Command base : commands) { + if (base.getName().equalsIgnoreCase(args[0])) { + return base.execute(sender, commandLabel, Arrays.copyOfRange(args, 1, args.length)); + } + } + } + + this.sendHelpMessage(sender); + return false; + } + + private void sendHelpMessage(CommandSender sender) { + sender.sendMessage(INFORMATION_MESSAGE); + + Stream uniqueCommands = SakuraCommands.COMMANDS.values() + .stream() + .filter(command -> command != this); + + uniqueCommands.forEach((command) -> { + sender.sendRichMessage(COMMAND_MSG, Placeholder.unparsed("command", command.getName())); + }); + + sender.sendMessage(Component.text("'", NamedTextColor.DARK_PURPLE)); + } + + @NotNull + @Override + public List tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException { + if (!this.testPermissionSilent(sender)) { + return Collections.emptyList(); + } + + return SakuraCommands.COMMANDS.values().stream() + .filter(command -> command != this) + .map(Command::getName) + .filter(name -> args.length <= 1 || name.startsWith(args[args.length - 1])) + .toList(); + } +} diff --git a/src/main/java/me/samsuik/sakura/command/SakuraCommands.java b/src/main/java/me/samsuik/sakura/command/SakuraCommands.java new file mode 100644 index 0000000000000000000000000000000000000000..3e85c19db0655034c203eaab8d2e6b5504da5da8 --- /dev/null +++ b/src/main/java/me/samsuik/sakura/command/SakuraCommands.java @@ -0,0 +1,22 @@ +package me.samsuik.sakura.command; + +import me.samsuik.sakura.command.subcommands.ConfigCommand; +import net.minecraft.server.MinecraftServer; +import org.bukkit.command.Command; + +import java.util.HashMap; +import java.util.Map; + +public final class SakuraCommands { + static final Map COMMANDS = new HashMap<>(); + static { + COMMANDS.put("sakura", new SakuraCommand("sakura")); + COMMANDS.put("config", new ConfigCommand("config")); + } + + public static void registerCommands(MinecraftServer server) { + COMMANDS.forEach((s, command) -> { + server.server.getCommandMap().register(s, "sakura", command); + }); + } +} diff --git a/src/main/java/me/samsuik/sakura/command/subcommands/ConfigCommand.java b/src/main/java/me/samsuik/sakura/command/subcommands/ConfigCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..c41f188fdaf510a127771b1782e957058b8cc355 --- /dev/null +++ b/src/main/java/me/samsuik/sakura/command/subcommands/ConfigCommand.java @@ -0,0 +1,33 @@ +package me.samsuik.sakura.command.subcommands; + +import me.samsuik.sakura.command.BaseSubCommand; +import net.minecraft.server.MinecraftServer; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.craftbukkit.CraftServer; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; + +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.format.NamedTextColor.GREEN; +import static net.kyori.adventure.text.format.NamedTextColor.RED; + +@DefaultQualifier(NonNull.class) +public final class ConfigCommand extends BaseSubCommand { + public ConfigCommand(String name) { + super(name); + this.description = "Command for reloading the sakura configuration file"; + } + + @Override + public void execute(CommandSender sender, String[] args) { + Command.broadcastCommandMessage(sender, text("Please note that this command is not supported and may cause issues.", RED)); + Command.broadcastCommandMessage(sender, text("If you encounter any issues please use the /stop command to restart your server.", RED)); + + MinecraftServer server = ((CraftServer) sender.getServer()).getServer(); + server.sakuraConfigurations.reloadConfigs(server); + server.server.reloadCount++; + + Command.broadcastCommandMessage(sender, text("Sakura config reload complete.", GREEN)); + } +} diff --git a/src/main/java/me/samsuik/sakura/configuration/GlobalConfiguration.java b/src/main/java/me/samsuik/sakura/configuration/GlobalConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..5296d4e0a1041932e36562f42fbf3e1dcfea33fe --- /dev/null +++ b/src/main/java/me/samsuik/sakura/configuration/GlobalConfiguration.java @@ -0,0 +1,64 @@ +package me.samsuik.sakura.configuration; + +import com.mojang.logging.LogUtils; +import io.papermc.paper.configuration.Configuration; +import io.papermc.paper.configuration.ConfigurationPart; +import org.slf4j.Logger; +import org.spongepowered.configurate.objectmapping.meta.Comment; +import org.spongepowered.configurate.objectmapping.meta.Setting; + +@SuppressWarnings({"CanBeFinal", "FieldCanBeLocal", "FieldMayBeFinal", "NotNullFieldNotInitialized", "InnerClassMayBeStatic", "RedundantSuppression"}) +public class GlobalConfiguration extends ConfigurationPart { + + private static final Logger LOGGER = LogUtils.getClassLogger(); + static final int CURRENT_VERSION = 1;// (when you change the version, change the comment, so it conflicts on rebases): rename filter bad nbt from spawn eggs + + 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; + + public Fps fps; + public class Fps extends ConfigurationPart { + public String message = "(S) "; + public String material = "pink_stained_glass_pane"; + } + + public Cannons cannons; + public class Cannons extends ConfigurationPart { + public Explosion explosion = new Explosion(); + + public class Explosion extends ConfigurationPart { + public boolean reducedSearchRays; + } + } + + public Players players; + public class Players extends ConfigurationPart { + public String potatoMessage = "(S) This block has of "; + } + + public Environment environment; + public class Environment extends ConfigurationPart { + @Comment("This is only intended for plot worlds. Will affect chunk generation on servers.") + public boolean calculateBiomeNoiseOncePerChunkSection = false; + + public MobSpawnerDefaults mobSpawnerDefaults = new MobSpawnerDefaults(); + public class MobSpawnerDefaults extends ConfigurationPart { + public int minSpawnDelay = 200; + public int maxSpawnDelay = 800; + public int spawnCount = 4; + public int maxNearbyEntities = 6; + public int requiredPlayerRange = 16; + public int spawnRange = 4; + } + } + +} diff --git a/src/main/java/me/samsuik/sakura/configuration/SakuraConfigurations.java b/src/main/java/me/samsuik/sakura/configuration/SakuraConfigurations.java new file mode 100644 index 0000000000000000000000000000000000000000..a22139d6f6775b7b8d635e126d2ea2bfd6227743 --- /dev/null +++ b/src/main/java/me/samsuik/sakura/configuration/SakuraConfigurations.java @@ -0,0 +1,254 @@ +package me.samsuik.sakura.configuration; + +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.mapping.InnerClassFieldDiscoverer; +import io.papermc.paper.configuration.serializer.*; +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.BooleanOrDefault; +import io.papermc.paper.configuration.type.Duration; +import io.papermc.paper.configuration.type.DurationOrDisabled; +import io.papermc.paper.configuration.type.number.DoubleOr; +import io.papermc.paper.configuration.type.number.IntOr; +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 me.samsuik.sakura.configuration.transformation.world.*; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceLocation; +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.block.Block; +import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.slf4j.Logger; +import org.spongepowered.configurate.ConfigurateException; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.ConfigurationOptions; +import org.spongepowered.configurate.objectmapping.FieldDiscoverer; +import org.spongepowered.configurate.objectmapping.ObjectMapper; +import org.spongepowered.configurate.transformation.ConfigurationTransformation; +import org.spongepowered.configurate.yaml.YamlConfigurationLoader; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.nio.file.Path; +import java.util.Map; +import java.util.function.Function; + +import static io.leangen.geantyref.GenericTypeReflector.erase; + +@SuppressWarnings("Convert2Diamond") +public class SakuraConfigurations extends Configurations { + + private static final Logger LOGGER = LogUtils.getClassLogger(); + static final String GLOBAL_CONFIG_FILE_NAME = "sakura-global.yml"; + static final String WORLD_DEFAULTS_CONFIG_FILE_NAME = "sakura-world-defaults.yml"; + static final String WORLD_CONFIG_FILE_NAME = "sakura-world.yml"; + public static final String CONFIG_DIR = "config"; + + private static final String GLOBAL_HEADER = String.format(""" + This is the global configuration file for Sakura. + 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. + + The world configuration options have been moved inside + their respective world folder. The files are named %s""", WORLD_CONFIG_FILE_NAME); + + private static final String WORLD_DEFAULTS_HEADER = """ + This is the world defaults configuration file for Sakura. + 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. + + Configuration options here apply to all worlds, unless you specify overrides inside + the world-specific config file inside each world folder."""; + + private static final Function WORLD_HEADER = map -> String.format(""" + This is a world configuration file for Sakura. + This file may start empty but can be filled with settings to override ones in the %s/%s + + World: %s (%s)""", + SakuraConfigurations.CONFIG_DIR, + SakuraConfigurations.WORLD_DEFAULTS_CONFIG_FILE_NAME, + map.require(WORLD_NAME), + map.require(WORLD_KEY) + ); + + public SakuraConfigurations(final Path globalFolder) { + super(globalFolder, GlobalConfiguration.class, WorldConfiguration.class, GLOBAL_CONFIG_FILE_NAME, WORLD_DEFAULTS_CONFIG_FILE_NAME, WORLD_CONFIG_FILE_NAME); + } + + @Override + protected YamlConfigurationLoader.Builder createLoaderBuilder() { + return super.createLoaderBuilder() + .defaultOptions(SakuraConfigurations::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(SakuraConfigurations::defaultGlobalOptions); + } + + private static ConfigurationOptions defaultGlobalOptions(ConfigurationOptions options) { + return options + .header(GLOBAL_HEADER) + .serializers(builder -> builder + .register(new PacketClassSerializer()) + .register(IntOr.Default.SERIALIZER) + ); + } + + @Override + public GlobalConfiguration initializeGlobalConfiguration(final RegistryAccess registryAccess) throws ConfigurateException { + GlobalConfiguration configuration = super.initializeGlobalConfiguration(registryAccess); + GlobalConfiguration.set(configuration); + return configuration; + } + + @Override + protected ObjectMapper.Factory.Builder createWorldObjectMapperFactoryBuilder(final ContextMap contextMap) { + return super.createWorldObjectMapperFactoryBuilder(contextMap) + .addNodeResolver(new NestedSetting.Factory()) + .addDiscoverer(createWorldConfigFieldDiscoverer(contextMap)); + } + + private static FieldDiscoverer createWorldConfigFieldDiscoverer(final ContextMap contextMap) { + final Map, Object> overrides = Map.of( + WorldConfiguration.class, createWorldConfigInstance(contextMap) + ); + return new InnerClassFieldDiscoverer(overrides); + } + + private static WorldConfiguration createWorldConfigInstance(ContextMap contextMap) { + return new WorldConfiguration( + contextMap.require(Configurations.WORLD_KEY) + ); + } + + @Override + protected YamlConfigurationLoader.Builder createWorldConfigLoaderBuilder(final ContextMap contextMap) { + final RegistryAccess access = contextMap.require(REGISTRY_ACCESS); + 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(StringRepresentableSerializer::isValidFor, new StringRepresentableSerializer()) + .register(IntOr.Default.SERIALIZER) + .register(IntOr.Disabled.SERIALIZER) + .register(DoubleOr.Default.SERIALIZER) + .register(BooleanOrDefault.SERIALIZER) + .register(Duration.SERIALIZER) + .register(DurationOrDisabled.SERIALIZER) + .register(NbtPathSerializer.SERIALIZER) + .register(new RegistryValueSerializer<>(new TypeToken>() {}, access, Registries.ENTITY_TYPE, true)) + .register(new RegistryValueSerializer<>(Item.class, access, Registries.ITEM, true)) + .register(new RegistryValueSerializer<>(Block.class, access, Registries.BLOCK, true)) + .register(new RegistryHolderSerializer<>(new TypeToken>() {}, access, Registries.CONFIGURED_FEATURE, false)) + ) + ); + } + + @Override + protected void applyWorldConfigTransformations(final ContextMap contextMap, final ConfigurationNode node, final @Nullable ConfigurationNode defaultsNode) throws ConfigurateException { + final ConfigurationTransformation.VersionedBuilder versionedBuilder = Transformations.versionedBuilder(); + V2_VerticalKnockbackUseDefault.apply(versionedBuilder); + V3_RenameKnockback.apply(versionedBuilder); + V4_RenameNonStrictMergeLevel.apply(versionedBuilder); + V5_CombineLoadChunksOptions.apply(versionedBuilder); + V6_FixIncorrectExtraKnockback.apply(versionedBuilder); + // ADD FUTURE VERSIONED TRANSFORMS TO versionedBuilder HERE + versionedBuilder.build().apply(node); + } + + @Override + public WorldConfiguration 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 world config for " + levelName, exception); + } + } + + @Override + protected boolean isConfigType(final Type type) { + return ConfigurationPart.class.isAssignableFrom(erase(type)); + } + + @Override + protected int globalConfigVersion() { + return GlobalConfiguration.CURRENT_VERSION; + } + + @Override + protected int worldConfigVersion() { + return WorldConfiguration.CURRENT_VERSION; + } + + public void reloadConfigs(MinecraftServer server) { + try { + this.initializeGlobalConfiguration(reloader(this.globalConfigClass, GlobalConfiguration.get())); + this.initializeWorldDefaultsConfiguration(server.registryAccess()); + for (ServerLevel level : server.getAllLevels()) { + this.createWorldConfig(createWorldContextMap(level), reloader(this.worldConfigClass, level.sakuraConfig())); + } + } catch (Exception ex) { + throw new RuntimeException("Could not reload paper configuration files", ex); + } + } + + private static ContextMap createWorldContextMap(ServerLevel level) { + return createWorldContextMap(level.convertable.levelDirectory.path(), level.serverLevelData.getLevelName(), level.dimension().location(), level.registryAccess()); + } + + public static ContextMap createWorldContextMap(Path dir, String levelName, ResourceLocation worldKey, RegistryAccess registryAccess) { + return ContextMap.builder() + .put(WORLD_DIRECTORY, dir) + .put(WORLD_NAME, levelName) + .put(WORLD_KEY, worldKey) + .put(REGISTRY_ACCESS, registryAccess) + .build(); + } + + public static SakuraConfigurations setup(final Path configDir) { + try { + PaperConfigurations.createDirectoriesSymlinkAware(configDir); + return new SakuraConfigurations(configDir); + } catch (final IOException ex) { + throw new RuntimeException("Could not setup PaperConfigurations", ex); + } + } + +} diff --git a/src/main/java/me/samsuik/sakura/configuration/WorldConfiguration.java b/src/main/java/me/samsuik/sakura/configuration/WorldConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..4a92dfb3e28ab4b97f000b67883d3320192745e3 --- /dev/null +++ b/src/main/java/me/samsuik/sakura/configuration/WorldConfiguration.java @@ -0,0 +1,219 @@ +package me.samsuik.sakura.configuration; + +import com.mojang.logging.LogUtils; +import io.papermc.paper.configuration.Configuration; +import io.papermc.paper.configuration.ConfigurationPart; +import io.papermc.paper.configuration.NestedSetting; +import io.papermc.paper.configuration.PaperConfigurations; +import io.papermc.paper.configuration.type.number.DoubleOr; +import io.papermc.paper.configuration.type.number.IntOr; +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; +import me.samsuik.sakura.entity.merge.MergeLevel; +import me.samsuik.sakura.explosion.durable.DurableMaterial; +import me.samsuik.sakura.physics.PhysicsVersion; +import net.minecraft.Util; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.item.FallingBlockEntity; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import org.slf4j.Logger; +import org.spongepowered.configurate.objectmapping.meta.Comment; +import org.spongepowered.configurate.objectmapping.meta.Setting; + +import java.util.List; +import java.util.Map; + +@SuppressWarnings({"FieldCanBeLocal", "FieldMayBeFinal", "NotNullFieldNotInitialized", "InnerClassMayBeStatic", "RedundantSuppression"}) +public class WorldConfiguration extends ConfigurationPart { + + private static final Logger LOGGER = LogUtils.getClassLogger(); + static final int CURRENT_VERSION = 6; // (when you change the version, change the comment, so it conflicts on rebases): rename filter bad nbt from spawn eggs + + private transient final ResourceLocation worldKey; + WorldConfiguration(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; + + public Cannons cannons; + public class Cannons extends ConfigurationPart { + public MergeLevel mergeLevel = MergeLevel.STRICT; + public boolean tntAndSandAffectedByBubbleColumns = true; + + @NestedSetting({"treat-collidable-blocks-as-full", "while-moving"}) + public boolean treatAllBlocksAsFullWhenMoving = false; + @NestedSetting({"treat-collidable-blocks-as-full", "moving-faster-than"}) + public double treatAllBlocksAsFullWhenMovingFasterThan = 64.0; + public boolean loadChunks = false; + + public Tnt tnt = new Tnt(); + public class Tnt extends ConfigurationPart { + public boolean forcePositionUpdates; + } + + public Sand sand = new Sand(); + public class Sand extends ConfigurationPart { + public boolean despawnInsideMovingPistons = true; + public boolean concreteSolidifyInWater = true; + + @NestedSetting({"prevent-stacking", "against-border"}) + public boolean preventAgainstBorder = false; + @NestedSetting({"prevent-stacking", "world-height"}) + public boolean preventAtWorldHeight = false; + + public boolean isFallingBlockInBounds(FallingBlockEntity entity) { + return (!this.preventAgainstBorder || !ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isCollidingWithBorder(entity.level().getWorldBorder(), entity.getBoundingBox().inflate(0.01))) + && (!this.preventAtWorldHeight || entity.blockPosition().getY() < entity.level().getMaxBuildHeight() - 1); + } + } + + public Explosion explosion = new Explosion(); + public class Explosion extends ConfigurationPart { + public boolean optimiseProtectedRegions = true; + public boolean avoidRedundantBlockSearches = false; + public Map durableMaterials = Util.make(new Reference2ObjectOpenHashMap<>(), map -> { + map.put(Blocks.OBSIDIAN, new DurableMaterial(4, Blocks.COBBLESTONE.getExplosionResistance())); + map.put(Blocks.ANVIL, new DurableMaterial(3, Blocks.END_STONE.getExplosionResistance())); + map.put(Blocks.CHIPPED_ANVIL, new DurableMaterial(3, Blocks.END_STONE.getExplosionResistance())); + map.put(Blocks.DAMAGED_ANVIL, new DurableMaterial(3, Blocks.END_STONE.getExplosionResistance())); + }); + public boolean protectScaffoldingFromCreepers = false; + public boolean allowNonTntBreakingDurableBlocks = false; + public boolean destroyWaterloggedBlocks = false; + public boolean explodeLava = false; + public boolean consistentRadius = false; + public boolean explosionsHurtPlayers = true; + public boolean explosionsDropItems = true; + public boolean useBlockCacheAcrossExplosions = false; + } + + public Mechanics mechanics = new Mechanics(); + public class Mechanics extends ConfigurationPart { + public TNTSpread tntSpread = TNTSpread.ALL; + public boolean tntFlowsInWater = true; + public boolean fallingBlockParity = false; + public PhysicsVersion physicsVersion = PhysicsVersion.LATEST; + + public enum TNTSpread { + ALL, Y, NONE; + } + } + } + + public Technical technical; + public class Technical extends ConfigurationPart { + public boolean dispenserRandomItemSelection = true; + + public Redstone redstone = new Redstone(); + public class Redstone extends ConfigurationPart { + public boolean redstoneCache = false; + public boolean fluidsBreakRedstone = true; + } + } + + public Players players; + public class Players extends ConfigurationPart { + public Combat combat = new Combat(); + public class Combat extends ConfigurationPart { + public boolean legacyCombatMechanics = false; + public boolean allowSweepAttacks = true; + public boolean shieldDamageReduction = false; + public boolean oldEnchantedGoldenApple = false; + public boolean oldSoundsAndParticleEffects = false; + public boolean fastHealthRegen = true; + } + + public Knockback knockback = new Knockback(); + public class Knockback extends ConfigurationPart { + public DoubleOr.Default knockbackVertical = DoubleOr.Default.USE_DEFAULT; + public double knockbackVerticalLimit = 0.4; + public boolean verticalKnockbackRequireGround = true; + public double baseKnockback = 0.4; + @Comment("Knockback caused by sweeping edge") + public double sweepingEdgeKnockback = 0.4; + + public Sprinting sprinting = new Sprinting(); + public class Sprinting extends ConfigurationPart { + public boolean requireFullAttack = true; + public double extraKnockback = 0.5; + @Comment("Delay between extra knockback hits in milliseconds") + public IntOr.Default knockbackDelay = IntOr.Default.USE_DEFAULT; + } + + @NestedSetting({"projectiles", "fishing-hooks-apply-knockback"}) + public boolean fishingHooksApplyKnockback; + + @Comment("Knockback resistance attribute modifier") + public double knockbackResistanceModifier = 1.0; + @Comment("Received by attacking a shielded enemy") + public double shieldHitKnockback = 0.5; + } + + @Comment("Prevents players swimming using elytra or riptide to enter holes") + public boolean posesShrinkCollisionBox = true; + public boolean fishingHooksPullEntities = true; + } + + public Entity entity; + public class Entity extends ConfigurationPart { + @Comment("Only modify if you know what you're doing") + public boolean disableMobAi = false; + public boolean waterSensitivity = true; + public boolean instantDeathAnimation = false; + public boolean ironGolemsTakeFalldamage = false; + + public Items items = new Items(); + public class Items extends ConfigurationPart { + public List explosionResistantItems = List.of(); + } + + @Comment("Entity travel distance limits") + public Map, Integer> chunkTravelLimit = Util.make(new Reference2ObjectOpenHashMap<>(), map -> { + map.put(EntityType.ENDER_PEARL, 8); + }); + + public ThrownPotion thrownPotion = new ThrownPotion(); + public class ThrownPotion extends ConfigurationPart { + public double horizontalSpeed = 1.0; + public double verticalSpeed = 1.0; + public boolean allowBreakingInsideEntities = false; + } + + public EnderPearl enderPearl = new EnderPearl(); + public class EnderPearl extends ConfigurationPart { + public boolean useOutlineForCollision = false; + } + } + + public Environment environment; + public class Environment extends ConfigurationPart { + public boolean allowWaterInTheNether = false; + public boolean disableFastNetherLava = false; + + public BlockGeneration blockGeneration = new BlockGeneration(); + public class BlockGeneration extends ConfigurationPart { + public boolean legacyBlockFormation = false; + } + + public Crops crops = new Crops(); + public class Crops extends ConfigurationPart { + public boolean useRandomChanceToGrow = false; + } + + public MobSpawner mobSpawner = new MobSpawner(); + public class MobSpawner extends ConfigurationPart { + public boolean checkSpawnConditions = true; + public boolean requireNearbyPlayer = true; + public boolean ignoreEntityLimit = false; + } + } + +} diff --git a/src/main/java/me/samsuik/sakura/configuration/transformation/world/V2_VerticalKnockbackUseDefault.java b/src/main/java/me/samsuik/sakura/configuration/transformation/world/V2_VerticalKnockbackUseDefault.java new file mode 100644 index 0000000000000000000000000000000000000000..97d5224ef110cb2fa4f44a90a54a1611dc0182d9 --- /dev/null +++ b/src/main/java/me/samsuik/sakura/configuration/transformation/world/V2_VerticalKnockbackUseDefault.java @@ -0,0 +1,31 @@ +package me.samsuik.sakura.configuration.transformation.world; + +import io.papermc.paper.configuration.type.number.DoubleOr; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.configurate.ConfigurateException; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.NodePath; +import org.spongepowered.configurate.transformation.ConfigurationTransformation; +import org.spongepowered.configurate.transformation.TransformAction; + +import static org.spongepowered.configurate.NodePath.path; + +public final class V2_VerticalKnockbackUseDefault implements TransformAction { + private static final int VERSION = 2; + private static final NodePath PATH = path("players", "knockback", "knockback-vertical"); + private static final V2_VerticalKnockbackUseDefault INSTANCE = new V2_VerticalKnockbackUseDefault(); + + private V2_VerticalKnockbackUseDefault() {} + + public static void apply(ConfigurationTransformation.VersionedBuilder builder) { + builder.addVersion(VERSION, ConfigurationTransformation.builder().addAction(PATH, INSTANCE).build()); + } + + @Override + public Object @Nullable [] visitPath(NodePath path, ConfigurationNode value) throws ConfigurateException { + if (value.getDouble() == 0.4) { + value.set(DoubleOr.Default.USE_DEFAULT); + } + return null; + } +} diff --git a/src/main/java/me/samsuik/sakura/configuration/transformation/world/V3_RenameKnockback.java b/src/main/java/me/samsuik/sakura/configuration/transformation/world/V3_RenameKnockback.java new file mode 100644 index 0000000000000000000000000000000000000000..efba1f83cb079d0e75904adc439b715d17793979 --- /dev/null +++ b/src/main/java/me/samsuik/sakura/configuration/transformation/world/V3_RenameKnockback.java @@ -0,0 +1,27 @@ +package me.samsuik.sakura.configuration.transformation.world; + +import org.spongepowered.configurate.NodePath; +import org.spongepowered.configurate.transformation.ConfigurationTransformation; + +import java.util.Map; + +import static org.spongepowered.configurate.NodePath.path; +import static org.spongepowered.configurate.transformation.TransformAction.*; + +public final class V3_RenameKnockback { + private static final int VERSION = 3; + private static final Map RENAME = Map.of( + path("players", "knockback", "vertical-limit-require-ground"), "vertical-knockback-require-ground", + path("players", "knockback", "knockback-horizontal"), "base-knockback" + ); + + private V3_RenameKnockback() {} + + public static void apply(ConfigurationTransformation.VersionedBuilder builder) { + ConfigurationTransformation.Builder transformationBuilder = ConfigurationTransformation.builder(); + for (Map.Entry entry : RENAME.entrySet()) { + transformationBuilder.addAction(entry.getKey(), rename(entry.getValue())); + } + builder.addVersion(VERSION, transformationBuilder.build()); + } +} diff --git a/src/main/java/me/samsuik/sakura/configuration/transformation/world/V4_RenameNonStrictMergeLevel.java b/src/main/java/me/samsuik/sakura/configuration/transformation/world/V4_RenameNonStrictMergeLevel.java new file mode 100644 index 0000000000000000000000000000000000000000..198eb5f030ec5def4d93dec51dde352261654bb5 --- /dev/null +++ b/src/main/java/me/samsuik/sakura/configuration/transformation/world/V4_RenameNonStrictMergeLevel.java @@ -0,0 +1,35 @@ +package me.samsuik.sakura.configuration.transformation.world; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.configurate.ConfigurateException; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.NodePath; +import org.spongepowered.configurate.transformation.ConfigurationTransformation; +import org.spongepowered.configurate.transformation.TransformAction; + +import java.util.Locale; + +import static org.spongepowered.configurate.NodePath.path; + +public final class V4_RenameNonStrictMergeLevel implements TransformAction { + private static final int VERSION = 4; + private static final String OLD_LEVEL_NAME = "NON_STRICT"; + private static final String NEW_LEVEL_NAME = "LENIENT"; + private static final NodePath PATH = path("cannons", "merge-level"); + private static final V4_RenameNonStrictMergeLevel INSTANCE = new V4_RenameNonStrictMergeLevel(); + + private V4_RenameNonStrictMergeLevel() {} + + public static void apply(ConfigurationTransformation.VersionedBuilder builder) { + builder.addVersion(VERSION, ConfigurationTransformation.builder().addAction(PATH, INSTANCE).build()); + } + + @Override + public Object @Nullable [] visitPath(NodePath path, ConfigurationNode value) throws ConfigurateException { + String level = value.getString(); + if (level != null && OLD_LEVEL_NAME.equals(level.toUpperCase(Locale.ENGLISH))) { + value.set(NEW_LEVEL_NAME); + } + return null; + } +} diff --git a/src/main/java/me/samsuik/sakura/configuration/transformation/world/V5_CombineLoadChunksOptions.java b/src/main/java/me/samsuik/sakura/configuration/transformation/world/V5_CombineLoadChunksOptions.java new file mode 100644 index 0000000000000000000000000000000000000000..3d647a3db0862232f158d823da9a797d4e0d5608 --- /dev/null +++ b/src/main/java/me/samsuik/sakura/configuration/transformation/world/V5_CombineLoadChunksOptions.java @@ -0,0 +1,44 @@ +package me.samsuik.sakura.configuration.transformation.world; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.configurate.ConfigurateException; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.NodePath; +import org.spongepowered.configurate.transformation.ConfigurationTransformation; +import org.spongepowered.configurate.transformation.TransformAction; + +import java.util.List; + +import static org.spongepowered.configurate.NodePath.path; + +public final class V5_CombineLoadChunksOptions implements TransformAction { + private static final int VERSION = 5; + private static final List ENTITY_PATHS = List.of("tnt", "sand"); + private static final String OLD_NAME = "loads-chunks"; + private static final String NAME = "load-chunks"; + private static final NodePath PATH = path("cannons"); + private static final V5_CombineLoadChunksOptions INSTANCE = new V5_CombineLoadChunksOptions(); + + private V5_CombineLoadChunksOptions() {} + + public static void apply(ConfigurationTransformation.VersionedBuilder builder) { + builder.addVersion(VERSION, ConfigurationTransformation.builder().addAction(PATH, INSTANCE).build()); + } + + @Override + public Object @Nullable [] visitPath(NodePath path, ConfigurationNode value) throws ConfigurateException { + boolean shouldLoadChunks = false; + + for (String entity : ENTITY_PATHS) { + NodePath entityPath = NodePath.path(entity, OLD_NAME); + if (value.hasChild(entityPath)) { + ConfigurationNode node = value.node(entityPath); + shouldLoadChunks |= node.getBoolean(); + node.raw(null); + } + } + + value.node(NAME).set(shouldLoadChunks); + return null; + } +} diff --git a/src/main/java/me/samsuik/sakura/configuration/transformation/world/V6_FixIncorrectExtraKnockback.java b/src/main/java/me/samsuik/sakura/configuration/transformation/world/V6_FixIncorrectExtraKnockback.java new file mode 100644 index 0000000000000000000000000000000000000000..f9fdf1a87420c78136deccf8822f5cf177b41aed --- /dev/null +++ b/src/main/java/me/samsuik/sakura/configuration/transformation/world/V6_FixIncorrectExtraKnockback.java @@ -0,0 +1,30 @@ +package me.samsuik.sakura.configuration.transformation.world; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.configurate.ConfigurateException; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.NodePath; +import org.spongepowered.configurate.transformation.ConfigurationTransformation; +import org.spongepowered.configurate.transformation.TransformAction; + +import static org.spongepowered.configurate.NodePath.path; + +public final class V6_FixIncorrectExtraKnockback implements TransformAction { + private static final int VERSION = 6; + private static final NodePath PATH = path("players", "knockback", "sprinting", "extra-knockback"); + private static final V6_FixIncorrectExtraKnockback INSTANCE = new V6_FixIncorrectExtraKnockback(); + + private V6_FixIncorrectExtraKnockback() {} + + public static void apply(ConfigurationTransformation.VersionedBuilder builder) { + builder.addVersion(VERSION, ConfigurationTransformation.builder().addAction(PATH, INSTANCE).build()); + } + + @Override + public Object @Nullable [] visitPath(NodePath path, ConfigurationNode value) throws ConfigurateException { + if (value.getDouble() == 1.0) { + value.set(0.5); + } + return null; + } +} diff --git a/src/main/java/me/samsuik/sakura/explosion/durable/DurableMaterial.java b/src/main/java/me/samsuik/sakura/explosion/durable/DurableMaterial.java new file mode 100644 index 0000000000000000000000000000000000000000..4024f9738e039ffffd560a07a2210f758879d3c0 --- /dev/null +++ b/src/main/java/me/samsuik/sakura/explosion/durable/DurableMaterial.java @@ -0,0 +1,7 @@ +package me.samsuik.sakura.explosion.durable; + +import org.spongepowered.configurate.objectmapping.ConfigSerializable; + +@ConfigSerializable +public record DurableMaterial(int durability, float resistance) { +} diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 9325d6f95165a7cee00d7de736af723681cc16b4..24356af82f9fd04c326d9c401219adbee7842e00 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -317,6 +317,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop minecraftserver.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig, minecraftserver.registryAccess(), iworlddataserver.getGameRules())), executor); // Paper - create paper world configs; 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, minecraftserver.registryAccess(), iworlddataserver.getGameRules())), () -> minecraftserver.sakuraConfigurations.createWorldConfig(me.samsuik.sakura.configuration.SakuraConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), minecraftserver.registryAccess())), executor); // Sakura - sakura configuration files // Paper - create paper world configs; Async-Anti-Xray: Pass executor 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 e2a0487089eb5a7bdc1433e4c75f69d8e9f9d5f9..7da2fc17f6e7bf888ef0c2a8eba0fc3b4c10436e 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java @@ -171,6 +171,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl return this.paperConfig; } // Paper end - add paper world config + // Sakura start - sakura configuration files + private final me.samsuik.sakura.configuration.WorldConfiguration sakuraConfig; + public final me.samsuik.sakura.configuration.WorldConfiguration sakuraConfig() { + return this.sakuraConfig; + } + // Sakura end - sakura configuration files public final com.destroystokyo.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray public final co.aikar.timings.WorldTimingsHandler timings; // Paper @@ -684,9 +690,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl } // Paper end - optimise random ticking - 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 - create paper world config & Anti-Xray + 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, Supplier sakuraWorldConfigCreator, java.util.concurrent.Executor executor) { // Sakura - sakura configuration files// Paper - create paper world config & Anti-Xray this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config + this.sakuraConfig = sakuraWorldConfigCreator.get(); // Sakura - sakura configuration files this.generator = gen; this.world = new CraftWorld((ServerLevel) this, gen, biomeProvider, env); diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java index c8b82bc41f2042bb4b067f06265a3a22e51f7629..bc85cb49643f053f567a36174a8b8f6809559061 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -1070,6 +1070,7 @@ public final class CraftServer implements Server { org.spigotmc.SpigotConfig.init((File) this.console.options.valueOf("spigot-settings")); // Spigot this.console.paperConfigurations.reloadConfigs(this.console); + this.console.sakuraConfigurations.reloadConfigs(this.console); // Sakura - missing comment above :< 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)) @@ -1101,6 +1102,7 @@ public final class CraftServer implements Server { this.reloadData(); org.spigotmc.SpigotConfig.registerCommands(); // Spigot io.papermc.paper.command.PaperCommands.registerCommands(this.console); // Paper + me.samsuik.sakura.command.SakuraCommands.registerCommands(this.console); // Sakura - sakura configuration files this.spark.registerCommandBeforePlugins(this); // Paper - spark this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*"); this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions"); diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java index 618f4b2e1d012b647b3e7bf359c8ecdd7f159e63..59f8e11c8b3a2442e75bb085c6ed648c97887db5 100644 --- a/src/main/java/org/bukkit/craftbukkit/Main.java +++ b/src/main/java/org/bukkit/craftbukkit/Main.java @@ -176,6 +176,14 @@ public class Main { .describedAs("Jar file"); // Paper end + // Sakura start - sakura configuration files + acceptsAll(asList("sakura-dir", "sakura-settings-directory"), "Directory for Sakura settings") + .withRequiredArg() + .ofType(File.class) + .defaultsTo(new File(me.samsuik.sakura.configuration.SakuraConfigurations.CONFIG_DIR)) + .describedAs("Config directory"); + // Sakura end - sakura configuration files + // Paper start acceptsAll(asList("server-name"), "Name of the server") .withRequiredArg()