9
0
mirror of https://github.com/Samsuik/Sakura.git synced 2025-12-22 00:09:20 +00:00
Files
SakuraMC/patches/server/0003-Sakura-Configuration-Files.patch
2025-03-14 18:45:24 +00:00

991 lines
47 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: "kfian294ma4@gmail.com" <kfian294ma4@gmail.com>
Date: Sun, 5 Sep 2021 18:01:34 +0100
Subject: [PATCH] Sakura Configuration Files
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..9b5af05f7a4593eb44f36fff90d94e98d6999c7f
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/command/BaseSubCommand.java
@@ -0,0 +1,47 @@
+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<String> list, String[] args) throws IllegalArgumentException {}
+
+ @Override
+ @Deprecated
+ public final boolean execute(CommandSender sender, String label, String[] args) {
+ if (testPermission(sender)) {
+ execute(sender, args);
+ }
+
+ return true;
+ }
+
+ @Override
+ @NotNull
+ public List<String> tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException {
+ var completions = new ArrayList<String>(0);
+
+ if (testPermissionSilent(sender)) {
+ 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..2b145614bf189ae56622016436bfefd63f5271eb
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/command/SakuraCommand.java
@@ -0,0 +1,93 @@
+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.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
+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 javax.annotation.Nullable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+@DefaultQualifier(NonNull.class)
+public final class SakuraCommand extends Command {
+
+ private static final Component INFORMATION_MESSAGE = MiniMessage.miniMessage().deserialize("""
+ <dark_purple>.</dark_purple>
+ <dark_purple>| <white>This is the main command for <gradient:red:light_purple:0.5>Sakura</gradient>.
+ <dark_purple>| <white>All exclusive commands are listed below."""
+ );
+
+ private static final String COMMAND_MSG = "<dark_purple>| <dark_gray>*</dark_gray> /<light_purple><command>";
+
+ 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) {
+ var commands = new ArrayList<>(SakuraCommands.COMMANDS.values());
+
+ // This part is copied from the VersionCommand SubCommand in paper
+ @Nullable
+ var internalVersion = MinecraftServer.getServer().server.getCommandMap().getCommand("version");
+ if (internalVersion != null) {
+ commands.add(internalVersion);
+ }
+
+ for (var base : commands) {
+ if (base.getName().equalsIgnoreCase(args[0])) {
+ return base.execute(sender, commandLabel, Arrays.copyOfRange(args, 1, args.length));
+ }
+ }
+ }
+
+ sendHelpMessage(sender);
+ return false;
+ }
+
+ private void sendHelpMessage(CommandSender sender) {
+ sender.sendMessage(INFORMATION_MESSAGE);
+
+ var uniqueCommands = SakuraCommands.COMMANDS.values()
+ .stream()
+ .filter(command -> command != this);
+
+ uniqueCommands.forEach((command) -> {
+ sender.sendMessage(MiniMessage.miniMessage().deserialize(COMMAND_MSG,
+ Placeholder.unparsed("command", command.getName()))
+ );
+ });
+
+ sender.sendMessage(Component.text("'", NamedTextColor.DARK_PURPLE));
+ }
+
+ @NotNull
+ @Override
+ public List<String> tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException {
+ if (!testPermissionSilent(sender)) {
+ return Collections.emptyList();
+ }
+
+ return SakuraCommands.COMMANDS.values().stream()
+ .filter(command -> command != this) // ahem
+ .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..cd44b3400a1ab9544aa4a9e50b1054ea436a3643
--- /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<String, Command> COMMANDS = new HashMap<>();
+ static {
+ COMMANDS.put("sakura", new SakuraCommand("sakura"));
+ COMMANDS.put("config", new ConfigCommand("config"));
+ }
+
+ public static void registerCommands(final 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..93c0f22cdf0c3b7ce4db55171ea397f5673231b1
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/command/subcommands/ConfigCommand.java
@@ -0,0 +1,50 @@
+package me.samsuik.sakura.command.subcommands;
+
+import me.samsuik.sakura.command.BaseSubCommand;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ServerLevel;
+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 org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+
+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();
+ me.samsuik.sakura.configuration.SakuraGlobalConfig.get().setup((File) server.options.valueOf("sakura-settings"));
+ for (ServerLevel world : server.getAllLevels()) {
+ world.sakuraConfig.init();
+ }
+ server.server.reloadCount++;
+
+ Command.broadcastCommandMessage(sender, text("Sakura config reload complete.", GREEN));
+ }
+
+ @NotNull
+ @Override
+ public List<String> tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException {
+ return Collections.emptyList();
+ }
+
+}
diff --git a/src/main/java/me/samsuik/sakura/configuration/SakuraGlobalConfig.java b/src/main/java/me/samsuik/sakura/configuration/SakuraGlobalConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..7000fa68886057fb742cf4f655a739b0ee3f62e1
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/configuration/SakuraGlobalConfig.java
@@ -0,0 +1,92 @@
+package me.samsuik.sakura.configuration;
+
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import java.io.File;
+
+public final class SakuraGlobalConfig {
+ private static final String HEADER = """
+ This is the main 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.
+
+ This configuration file is not compatible with Sakura for modern versions of minecraft.
+ """;
+
+ private static final SakuraGlobalConfig INSTANCE = new SakuraGlobalConfig();
+
+ public static SakuraGlobalConfig get() {
+ return INSTANCE;
+ }
+
+ private YamlConfiguration config;
+ private int version;
+
+ public void setup(File configFile) {
+ this.config = YamlConfigHelper.loadConfig(configFile, HEADER, config -> {
+ this.version = this.getInt("config-version", 0);
+ this.set("config-version", 0);
+ });
+ }
+
+ public YamlConfiguration getYamlConfig() {
+ return this.config;
+ }
+
+ private void set(String path, Object val) {
+ this.config.set(path, val);
+ }
+
+ private boolean getBoolean(String path, boolean def) {
+ this.config.addDefault(path, def);
+ return this.config.getBoolean(path, this.config.getBoolean(path));
+ }
+
+ private int getInt(String path, int def) {
+ this.config.addDefault(path, def);
+ return this.config.getInt(path, this.config.getInt(path));
+ }
+
+ private String getString(String path, String def) {
+ this.config.addDefault(path, def);
+ return this.config.getString(path, this.config.getString(path));
+ }
+
+ public String fpsMessage = "<dark_gray>(<light_purple>S</light_purple>) <gray><state> <yellow><name>";
+ public String fpsMaterial = "pink_stained_glass_pane";
+ private void Fps() {
+ this.fpsMessage = this.getString("fps.messsage", this.fpsMessage);
+ this.fpsMaterial = this.getString("fps.material", this.fpsMaterial);
+ }
+
+ public boolean reducedSearchRays;
+ private void Explosion() {
+ this.reducedSearchRays = this.getBoolean("cannons.explosion.reduced-search-rays", this.reducedSearchRays);
+ }
+
+ public String potatoMessage = "<dark_gray>(<light_purple>S</light_purple>) <white>This block has <gray><remaining></gray> of <gray><durability>";
+ private void Players() {
+ this.potatoMessage = this.getString("players.potato-message", this.potatoMessage);
+ }
+
+ public boolean calculateBiomeNoiseOncePerChunkSection = false;
+ private void Environment() {
+ this.calculateBiomeNoiseOncePerChunkSection = this.getBoolean("environment.calculate-biome-noise-once-per-chunk-section", this.calculateBiomeNoiseOncePerChunkSection);
+ }
+
+ public int minSpawnDelay = 200;
+ public int maxSpawnDelay = 800;
+ public int spawnCount = 4;
+ public int maxNearbyEntities = 6;
+ public int requiredPlayerRange = 16;
+ public int spawnRange = 4;
+ private void MobSpawnerDefaults() {
+ // as global and world configs are combined I moved the defaults into its own section
+ this.minSpawnDelay = this.getInt("environment.mob-spawner.defaults.min-spawn-delay", this.minSpawnDelay);
+ this.maxSpawnDelay = this.getInt("environment.mob-spawner.defaults.max-spawn-delay", this.maxSpawnDelay);
+ this.spawnCount = this.getInt("environment.mob-spawner.defaults.spawn-count", this.spawnCount);
+ this.maxNearbyEntities = this.getInt("environment.mob-spawner.defaults.max-nearby-entities", this.maxNearbyEntities);
+ this.requiredPlayerRange = this.getInt("environment.mob-spawner.defaults.required-player-range", this.requiredPlayerRange);
+ this.spawnRange = this.getInt("environment.mob-spawner.defaults.spawn-range", this.spawnRange);
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/configuration/SakuraWorldConfig.java b/src/main/java/me/samsuik/sakura/configuration/SakuraWorldConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..34c4d0db007753f37cc4ca96c21175ce0f0d56e1
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/configuration/SakuraWorldConfig.java
@@ -0,0 +1,325 @@
+package me.samsuik.sakura.configuration;
+
+import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
+import me.samsuik.sakura.configuration.serializer.*;
+import me.samsuik.sakura.entity.TntSpread;
+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.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.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.MemoryConfiguration;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import java.util.*;
+
+public final class SakuraWorldConfig {
+ private final String worldName;
+ private YamlConfiguration config;
+
+ public SakuraWorldConfig(String worldName) {
+ this.worldName = worldName;
+ this.init();
+ }
+
+ public void init() {
+ this.config = SakuraGlobalConfig.get().getYamlConfig(); // grab updated reference
+ }
+
+ private String worldPath(String path) {
+ return "world-settings." + this.worldName + "." + path;
+ }
+
+ private void set(String path, Object val) {
+ this.config.set(path, val);
+ if (this.config.get(this.worldPath(path)) != null) {
+ this.config.set(this.worldPath(path), val);
+ }
+ }
+
+ private void remove(String path) {
+ this.config.addDefault(path, null);
+ this.set(path, null);
+ }
+
+ private boolean getBoolean(String path, boolean def) {
+ this.config.addDefault(path, def);
+ return this.config.getBoolean(this.worldPath(path), this.config.getBoolean(path, def));
+ }
+
+ private double getDouble(String path, double def) {
+ this.config.addDefault(path, def);
+ return this.config.getDouble(this.worldPath(path), this.config.getDouble(path));
+ }
+
+ private int getInt(String path, int def) {
+ this.config.addDefault(path, def);
+ return this.config.getInt(this.worldPath(path), this.config.getInt(path, def));
+ }
+
+ private <T> List<T> getList(String path, List<T> def) {
+ this.config.addDefault(path, def);
+ return (List<T>) this.config.getList(this.worldPath(path), this.config.getList(path));
+ }
+
+ private String getString(String path, String def) {
+ this.config.addDefault(path, def);
+ return this.config.getString(this.worldPath(path), this.config.getString(path));
+ }
+
+ private <T extends Enum<T>> T getEnum(String path, T def, Class<T> enumClass) {
+ String name = this.getString(path, def.name());
+ return Enum.valueOf(enumClass, name);
+ }
+
+ private OptionalDouble getDoubleWithContext(String path, String context) {
+ this.config.addDefault(path, context);
+ double present = this.config.getDouble(path, this.config.getDouble(this.worldPath(path), Double.MIN_VALUE));
+ return present != Double.MIN_VALUE ? OptionalDouble.of(present) : OptionalDouble.empty();
+ }
+
+ private OptionalInt getIntWithContext(String path, String context) {
+ this.config.addDefault(path, context);
+ int present = this.config.getInt(path, this.config.getInt(this.worldPath(path), Integer.MIN_VALUE));
+ return present != Integer.MIN_VALUE ? OptionalInt.of(present) : OptionalInt.empty();
+ }
+
+ private <K, V, C> void loadSerializedMap(String path, Map<K, V> def, MapSerializer<K, V, C> serializer) {
+ this.config.addDefault(path, this.getSerializedMapDefaults(def, serializer));
+ ConfigurationSection section = this.config.getConfigurationSection(path);
+ def.clear();
+ for (String serializedKey : section.getKeys(false)) {
+ K key = serializer.deserializeKey(serializedKey);
+ V value = serializer.deserializeValue((C) this.config.get(serializedKey));
+ def.put(key, value);
+ }
+ }
+
+ private <K, V> ConfigurationSection getSerializedMapDefaults(Map<K, V> def, MapSerializer<K, V, ?> serializer) {
+ ConfigurationSection section = new MemoryConfiguration();
+ def.forEach((k, v) -> {
+ String key = serializer.serializeKey(k);
+ Object value = serializer.serializeValue(v);
+ section.set(key, value);
+ });
+ return section;
+ }
+
+ private <V, C> void loadSerializedList(String path, List<V> def, ListSerializer<V, C> serializer) {
+ List<C> defaults = def.stream()
+ .map(serializer::serialize)
+ .toList();
+ def.clear();
+ for (C object : this.getList(path, defaults)) {
+ def.add(serializer.deserialize(object));
+ }
+ }
+
+ public MergeLevel mergeLevel = MergeLevel.STRICT;
+ public boolean tntAndSandAffectedByBubbleColumns = true;
+ public boolean loadChunks = false;
+ public boolean collideWhenMoving;
+ public double collideMovingFasterThan = 64.0;
+ private void Cannons() {
+ this.mergeLevel = this.getEnum("cannons.merge-level", this.mergeLevel, MergeLevel.class);
+ this.tntAndSandAffectedByBubbleColumns = this.getBoolean("cannons.tnt-and-sand-affected-by-bubble-columns", this.tntAndSandAffectedByBubbleColumns);
+ this.loadChunks = this.getBoolean("cannons.load-chunks", this.loadChunks);
+
+ this.collideWhenMoving = this.getBoolean("cannons.treat-collidable-blocks-as-full.while-moving", this.collideWhenMoving);
+ this.collideMovingFasterThan = this.getDouble("cannons.treat-collidable-blocks-as-full.moving-faster-than", this.collideMovingFasterThan);
+ }
+
+ public OptionalInt leftShootingThreshold = OptionalInt.empty();
+ public OptionalInt maxAdjustDistance = OptionalInt.empty();
+ private void Restrictions() {
+ this.leftShootingThreshold = this.getIntWithContext("cannons.restrictions.left-shooting-threshold", "disabled");
+ this.maxAdjustDistance = this.getIntWithContext("cannons.restrictions.max-adjust-distance", "disabled");
+ }
+
+ public boolean forcePositionUpdates;
+ private void Tnt() {
+ this.forcePositionUpdates = this.getBoolean("cannons.tnt.force-position-updates", this.forcePositionUpdates);
+ }
+
+ public boolean despawnInsideMovingPistons = true;
+ public boolean concreteSolidifyInWater = true;
+ public boolean preventStackingAgainstBorder = false;
+ public boolean preventStackingAtWorldHeight = false;
+ private void Sand() {
+ this.despawnInsideMovingPistons = this.getBoolean("cannons.sand.despawn-inside-moving-pistons", this.despawnInsideMovingPistons);
+ this.concreteSolidifyInWater = this.getBoolean("cannons.sand.concrete-solidify-in-water", this.concreteSolidifyInWater);
+ this.preventStackingAgainstBorder = this.getBoolean("cannons.sand.prevent-stacking.against-border", this.preventStackingAgainstBorder);
+ this.preventStackingAtWorldHeight = this.getBoolean("cannons.sand.prevent-stacking.world-height", this.preventStackingAtWorldHeight);
+ }
+
+ public boolean isFallingBlockInBounds(FallingBlockEntity entity) {
+ if (this.preventStackingAtWorldHeight && entity.blockPosition().getY() >= entity.level.getMaxBuildHeight()) {
+ return false;
+ }
+ if (this.preventStackingAgainstBorder && io.papermc.paper.util.CollisionUtil.isCollidingWithBorderEdge(entity.level.getWorldBorder(), entity.getBoundingBox().inflate(0.01))) {
+ return false;
+ }
+ return true;
+ }
+
+ public boolean optimiseProtectedRegions = true;
+ public boolean avoidRedundantBlockSearches = false;
+ public Map<Block, DurableMaterial> 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;
+ private void Explosion() {
+ this.optimiseProtectedRegions = this.getBoolean("cannons.explosion.optimise-protected-regions", this.optimiseProtectedRegions);
+ this.avoidRedundantBlockSearches = this.getBoolean("cannons.explosion.avoid-redundant-block-searches", this.avoidRedundantBlockSearches);
+ this.loadSerializedMap("cannons.explosion.durable-materials", this.durableMaterials, DurableMaterialSerializer.get());
+ this.protectScaffoldingFromCreepers = this.getBoolean("cannons.explosion.protect-scaffolding-from-creepers", this.protectScaffoldingFromCreepers);
+ this.allowNonTntBreakingDurableBlocks = this.getBoolean("cannons.explosion.allow-non-tnt-breaking-durable-blocks", this.allowNonTntBreakingDurableBlocks);
+ this.destroyWaterloggedBlocks = this.getBoolean("cannons.explosion.destroy-waterlogged-blocks", this.destroyWaterloggedBlocks);
+ this.explodeLava = this.getBoolean("cannons.explosion.explode-lava", this.explodeLava);
+ this.consistentRadius = this.getBoolean("cannons.explosion.consistent-radius", this.consistentRadius);
+ this.explosionsHurtPlayers = this.getBoolean("cannons.explosion.explosions-hurt-players", this.explosionsHurtPlayers);
+ this.explosionsDropItems = this.getBoolean("cannons.explosion.explosions-drop-items", this.explosionsDropItems);
+ }
+
+ public TntSpread tntSpread = TntSpread.ALL;
+ public boolean tntFlowsInWater = true;
+ public boolean fallingBlockParity = false;
+ public PhysicsVersion physicsVersion = PhysicsVersion.LATEST;
+ private void Mechanics() {
+ this.tntSpread = this.getEnum("cannons.mechanics.tnt-spread", this.tntSpread, TntSpread.class);
+ this.tntFlowsInWater = this.getBoolean("cannons.mechanics.tnt-flows-in-water", this.tntFlowsInWater);
+ this.fallingBlockParity = this.getBoolean("cannons.mechanics.falling-block-parity", this.fallingBlockParity);
+ this.physicsVersion = this.getEnum("cannons.mechanics.physics-version", this.physicsVersion, PhysicsVersion.class);
+ }
+
+ public boolean dispenserRandomItemSelection = true;
+ public boolean fluidsBreakRedstone = true;
+ private void Technical() {
+ this.dispenserRandomItemSelection = this.getBoolean("technical.dispenser-random-item-selection", this.dispenserRandomItemSelection);
+ this.fluidsBreakRedstone = this.getBoolean("technical.redstone.fluids-break-redstone", this.fluidsBreakRedstone);
+ }
+
+ 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 OptionalInt maxArmourDamage = OptionalInt.empty();
+ private void Combat() {
+ this.legacyCombatMechanics = this.getBoolean("players.combat.legacy-combat-mechanics", this.legacyCombatMechanics);
+ this.allowSweepAttacks = this.getBoolean("players.combat.allow-sweep-attacks", this.allowSweepAttacks);
+ this.shieldDamageReduction = this.getBoolean("players.combat.shield-damage-reduction", this.shieldDamageReduction);
+ this.oldEnchantedGoldenApple = this.getBoolean("players.combat.old-enchanted-golden-apple", this.oldEnchantedGoldenApple);
+ this.oldSoundsAndParticleEffects = this.getBoolean("players.combat.old-sounds-and-particle-effects", this.oldSoundsAndParticleEffects);
+ this.fastHealthRegen = this.getBoolean("players.combat.fast-health-regen", this.fastHealthRegen);
+ this.maxArmourDamage = this.getIntWithContext("players.combat.max-armour-damage", "default");
+ }
+
+ public OptionalDouble knockbackVertical = OptionalDouble.empty();
+ public double knockbackVerticalLimit = 0.4;
+ public boolean verticalKnockbackRequireGround = true;
+ public double baseKnockback = 0.4;
+ public double sweepingEdgeKnockback = 0.4;
+ public boolean sprintingRequireFullAttack = true;
+ public double sprintingExtraKnockback = 0.5;
+ public OptionalInt sprintingKnockbackDelay = OptionalInt.empty();
+ public boolean fishingHooksApplyKnockback;
+ public double knockbackResistanceModifier = 1.0;
+ public double shieldHitKnockback = 0.5;
+ private void Knockback() {
+ this.knockbackVertical = this.getDoubleWithContext("players.knockback.knockback-vertical", "default");
+ this.knockbackVerticalLimit = this.getDouble("players.knockback.knockback-vertical-limit", this.knockbackVerticalLimit);
+ this.verticalKnockbackRequireGround = this.getBoolean("players.knockback.vertical-knockback-require-ground", this.verticalKnockbackRequireGround);
+ this.baseKnockback = this.getDouble("players.knockback.base-knockback", this.baseKnockback);
+ this.sweepingEdgeKnockback = this.getDouble("players.knockback.sweeping-edge-knockback", this.sweepingEdgeKnockback);
+
+ this.sprintingRequireFullAttack = this.getBoolean("players.knockback.sprinting.require-full-attack", this.sprintingRequireFullAttack);
+ this.sprintingExtraKnockback = this.getDouble("players.knockback.sprinting.extra-knockback", this.sprintingExtraKnockback);
+ this.sprintingKnockbackDelay = this.getIntWithContext("players.knockback.sprinting.knockback-delay", "default");
+
+ this.fishingHooksApplyKnockback = this.getBoolean("players.knockback.projectiles.fishing-hooks-apply-knockback", this.fishingHooksApplyKnockback);
+ this.knockbackResistanceModifier = this.getDouble("players.knockback.knockback-resistance-modifier", this.knockbackResistanceModifier);
+ this.shieldHitKnockback = this.getDouble("players.knockback.shield-hit-knockback", this.shieldHitKnockback);
+ }
+
+ public boolean posesShrinkCollisionBox = true;
+ public boolean fishingHooksPullEntities = true;
+ private void Players() {
+ this.posesShrinkCollisionBox = this.getBoolean("players.poses-shrink-collision-box", this.posesShrinkCollisionBox);
+ this.fishingHooksPullEntities = this.getBoolean("players.fishing-hooks-pull-entities", this.fishingHooksPullEntities);
+ }
+
+ public boolean disableMobAi = false;
+ public boolean waterSensitivity = true;
+ public boolean instantDeathAnimation = false;
+ public boolean ironGolemsTakeFalldamage = false;
+ public Map<EntityType<?>, Integer> chunkTravelLimit = Util.make(new Reference2ObjectOpenHashMap<>(), map -> {
+ map.put(EntityType.ENDER_PEARL, 8);
+ });
+ public boolean projectilesLoadChunksForCollisions = false;
+ public List<Item> explosionResistantItems = List.of();
+ private void Entity() {
+ this.disableMobAi = this.getBoolean("entity.disable-mob-ai", this.disableMobAi);
+ this.waterSensitivity = this.getBoolean("entity.water-sensitivity", this.waterSensitivity);
+ this.instantDeathAnimation = this.getBoolean("entity.instant-death-animation", this.instantDeathAnimation);
+ this.ironGolemsTakeFalldamage = this.getBoolean("entity.iron-golems-take-falldamage", this.ironGolemsTakeFalldamage);
+ this.loadSerializedMap("entity.chunk-travel-limit", this.chunkTravelLimit, ChunkTravelLimitsSerializer.get());
+ this.projectilesLoadChunksForCollisions = this.getBoolean("entity.projectiles-load-chunks-for-collisions", this.projectilesLoadChunksForCollisions);
+ this.loadSerializedList("entity.items.explosion-resistant-items", this.explosionResistantItems, ItemListSerializer.get());
+ }
+
+ public double horizontalSpeed = 1.0;
+ public double verticalSpeed = 1.0;
+ public boolean allowBreakingInsideEntities = false;
+ private void ThrownPotion() {
+ this.horizontalSpeed = this.getDouble("entity.thrown-potion.horizontal-speed", this.horizontalSpeed);
+ this.verticalSpeed = this.getDouble("entity.thrown-potion.vertical-speed", this.verticalSpeed);
+ this.allowBreakingInsideEntities = this.getBoolean("entity.thrown-potion.allow-breaking-inside-entities", this.allowBreakingInsideEntities);
+ }
+
+ public boolean useOutlineForCollision = false;
+ private void EnderPearl() {
+ this.useOutlineForCollision = this.getBoolean("entity.ender-pearl.use-outline-for-collision", this.useOutlineForCollision);
+ }
+
+ public boolean allowWaterInTheNether = false;
+ public boolean disableFastNetherLava = false;
+ private void Environment() {
+ this.allowWaterInTheNether = this.getBoolean("environment.allow-water-in-the-nether", this.allowWaterInTheNether);
+ this.disableFastNetherLava = this.getBoolean("environment.disable-fast-nether-lava", this.disableFastNetherLava);
+ }
+
+ public boolean legacyBlockFormation = false;
+ private void BlockGeneration() {
+ this.legacyBlockFormation = this.getBoolean("environment.block-generation.legacy-block-formation", this.legacyBlockFormation);
+ }
+
+ public boolean useRandomChanceToGrow = false;
+ private void Crops() {
+ this.useRandomChanceToGrow = this.getBoolean("environment.crops.use-random-chance-to-grow", this.useRandomChanceToGrow);
+ }
+
+ public boolean checkSpawnConditions = true;
+ public boolean requireNearbyPlayer = true;
+ public boolean ignoreEntityLimit = false;
+ private void MobSpawner() {
+ this.checkSpawnConditions = this.getBoolean("environment.mob-spawner.check-spawn-conditions", this.checkSpawnConditions);
+ this.requireNearbyPlayer = this.getBoolean("environment.mob-spawner.require-nearby-player", this.requireNearbyPlayer);
+ this.ignoreEntityLimit = this.getBoolean("environment.mob-spawner.ignore-entity-limit", this.ignoreEntityLimit);
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/configuration/YamlConfigHelper.java b/src/main/java/me/samsuik/sakura/configuration/YamlConfigHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..9340a0696d2a1bf4d91d67627df49191a40682b6
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/configuration/YamlConfigHelper.java
@@ -0,0 +1,60 @@
+package me.samsuik.sakura.configuration;
+
+import com.google.common.base.Throwables;
+import org.bukkit.Bukkit;
+import org.bukkit.configuration.InvalidConfigurationException;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.function.Consumer;
+import java.util.logging.Level;
+
+public final class YamlConfigHelper {
+ public static YamlConfiguration loadConfig(File configFile, String header, Consumer<YamlConfiguration> afterLoad) {
+ YamlConfiguration config = new YamlConfiguration();
+ try {
+ config.load(configFile);
+ } catch (IOException ignored) {
+ } catch (InvalidConfigurationException ex) {
+ Bukkit.getLogger().log(Level.SEVERE, String.format("Could not load %s, please correct your syntax errors", configFile.getName()), ex);
+ throw Throwables.propagate(ex);
+ }
+
+ config.options().header(header);
+ config.options().copyDefaults(true);
+ afterLoad.accept(config);
+
+ readConfig(SakuraGlobalConfig.class, null);
+ saveConfig(config, configFile);
+ return config;
+ }
+
+ public static void readConfig(Class<?> clazz, Object instance) {
+ for (Method method : clazz.getDeclaredMethods()) {
+ if (Modifier.isPrivate(method.getModifiers())) {
+ if (method.getParameterTypes().length == 0 && method.getReturnType() == Void.TYPE) {
+ try {
+ method.setAccessible(true);
+ method.invoke(instance);
+ } catch (InvocationTargetException ex) {
+ throw Throwables.propagate(ex.getCause());
+ } catch (Exception ex) {
+ Bukkit.getLogger().log(Level.SEVERE, "Error invoking " + method, ex);
+ }
+ }
+ }
+ }
+ }
+
+ public static void saveConfig(YamlConfiguration config, File configFile) {
+ try {
+ config.save(configFile);
+ } catch (IOException ex) {
+ Bukkit.getLogger().log(Level.SEVERE, "Could not save " + configFile, ex);
+ }
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/configuration/serializer/ChunkTravelLimitsSerializer.java b/src/main/java/me/samsuik/sakura/configuration/serializer/ChunkTravelLimitsSerializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..37be21e53f33c1aa64e1919092daa4cb0b1515c4
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/configuration/serializer/ChunkTravelLimitsSerializer.java
@@ -0,0 +1,33 @@
+package me.samsuik.sakura.configuration.serializer;
+
+import net.minecraft.core.Registry;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.world.entity.EntityType;
+
+public final class ChunkTravelLimitsSerializer implements MapSerializer<EntityType<?>, Integer, Integer> {
+ private static final ChunkTravelLimitsSerializer INSTANCE = new ChunkTravelLimitsSerializer();
+
+ public static ChunkTravelLimitsSerializer get() {
+ return INSTANCE;
+ }
+
+ @Override
+ public String serializeKey(EntityType<?> key) {
+ return Registry.ENTITY_TYPE.getKey(key).getPath();
+ }
+
+ @Override
+ public EntityType<?> deserializeKey(String string) {
+ return Registry.ENTITY_TYPE.get(new ResourceLocation(string));
+ }
+
+ @Override
+ public Integer serializeValue(Integer val) {
+ return val;
+ }
+
+ @Override
+ public Integer deserializeValue(Integer val) {
+ return val;
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/configuration/serializer/DurableMaterialSerializer.java b/src/main/java/me/samsuik/sakura/configuration/serializer/DurableMaterialSerializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..0b1670515231be1408780fa6d4cc2daa599b024c
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/configuration/serializer/DurableMaterialSerializer.java
@@ -0,0 +1,41 @@
+package me.samsuik.sakura.configuration.serializer;
+
+import me.samsuik.sakura.explosion.durable.DurableMaterial;
+import net.minecraft.core.Registry;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.world.level.block.Block;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.MemoryConfiguration;
+
+public final class DurableMaterialSerializer implements MapSerializer<Block, DurableMaterial, ConfigurationSection> {
+ private static final DurableMaterialSerializer INSTANCE = new DurableMaterialSerializer();
+
+ public static DurableMaterialSerializer get() {
+ return INSTANCE;
+ }
+
+ @Override
+ public String serializeKey(Block key) {
+ return Registry.BLOCK.getKey(key).getPath();
+ }
+
+ @Override
+ public Block deserializeKey(String blockName) {
+ return Registry.BLOCK.get(new ResourceLocation(blockName));
+ }
+
+ @Override
+ public ConfigurationSection serializeValue(DurableMaterial mat) {
+ MemoryConfiguration materialSection = new MemoryConfiguration();
+ materialSection.set("durability", mat.durability());
+ materialSection.set("resistance", mat.resistance());
+ return materialSection;
+ }
+
+ @Override
+ public DurableMaterial deserializeValue(ConfigurationSection materialSection) {
+ int durability = materialSection.getInt("durability", 0);
+ float resistance = (float) materialSection.getDouble("resistance", 0.0);
+ return new DurableMaterial(durability, resistance);
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/configuration/serializer/ItemListSerializer.java b/src/main/java/me/samsuik/sakura/configuration/serializer/ItemListSerializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..1619656cd9d776ce6abb5de15364cc6cf1ba8f12
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/configuration/serializer/ItemListSerializer.java
@@ -0,0 +1,23 @@
+package me.samsuik.sakura.configuration.serializer;
+
+import net.minecraft.core.Registry;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.world.item.Item;
+
+public final class ItemListSerializer implements ListSerializer<Item, String> {
+ private static final ItemListSerializer INSTANCE = new ItemListSerializer();
+
+ public static ItemListSerializer get() {
+ return INSTANCE;
+ }
+
+ @Override
+ public String serialize(Item val) {
+ return Registry.ITEM.getKey(val).getPath();
+ }
+
+ @Override
+ public Item deserialize(String any) {
+ return Registry.ITEM.get(new ResourceLocation(any));
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/configuration/serializer/ListSerializer.java b/src/main/java/me/samsuik/sakura/configuration/serializer/ListSerializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..0937a8dd5b8dcb1c594c0afb82b5dfda3f6c8305
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/configuration/serializer/ListSerializer.java
@@ -0,0 +1,7 @@
+package me.samsuik.sakura.configuration.serializer;
+
+public interface ListSerializer<V, C> {
+ C serialize(V val);
+
+ V deserialize(C any);
+}
diff --git a/src/main/java/me/samsuik/sakura/configuration/serializer/MapSerializer.java b/src/main/java/me/samsuik/sakura/configuration/serializer/MapSerializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..7eec08f2263ec1eef5571bcef33bb01771b36833
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/configuration/serializer/MapSerializer.java
@@ -0,0 +1,13 @@
+package me.samsuik.sakura.configuration.serializer;
+
+public interface MapSerializer<K, V, C> {
+
+
+ String serializeKey(K key);
+
+ K deserializeKey(String string);
+
+ C serializeValue(V val);
+
+ V deserializeValue(C object);
+}
diff --git a/src/main/java/me/samsuik/sakura/entity/TntSpread.java b/src/main/java/me/samsuik/sakura/entity/TntSpread.java
new file mode 100644
index 0000000000000000000000000000000000000000..83275db7686a656f8d932d03e092cc6ddaf31ebb
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/entity/TntSpread.java
@@ -0,0 +1,5 @@
+package me.samsuik.sakura.entity;
+
+public enum TntSpread {
+ ALL, Y, NONE;
+}
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/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
index 257c94f7c1cb00c9a91ab82e311dfd8eca29c538..edd21098ee4c2773acf61fd073d8c87fe2880201 100644
--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
@@ -235,6 +235,10 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
io.papermc.paper.util.ObfHelper.INSTANCE.getClass(); // load mappings for stacktrace deobf and etc.
io.papermc.paper.brigadier.PaperBrigadierProviderImpl.INSTANCE.getClass(); // init PaperBrigadierProvider
// Paper end
+ // Sakura start
+ me.samsuik.sakura.configuration.SakuraGlobalConfig.get().setup((java.io.File) this.options.valueOf("sakura-settings"));
+ me.samsuik.sakura.command.SakuraCommands.registerCommands(this);
+ // Sakura end
this.setPvpAllowed(dedicatedserverproperties.pvp);
this.setFlightAllowed(dedicatedserverproperties.allowFlight);
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
index fee8996f35b38fd79946cdfd677763e0201eb57d..db7335a079cf711a829b2e4fb7354717da2f8789 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -162,6 +162,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
public final org.spigotmc.SpigotWorldConfig spigotConfig; // Spigot
public final com.destroystokyo.paper.PaperWorldConfig paperConfig; // Paper
+ public final me.samsuik.sakura.configuration.SakuraWorldConfig sakuraConfig; // Sakura
public final com.destroystokyo.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray
public final co.aikar.timings.WorldTimingsHandler timings; // Paper
@@ -264,6 +265,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
protected Level(WritableLevelData worlddatamutable, ResourceKey<Level> resourcekey, Holder<DimensionType> holder, Supplier<ProfilerFiller> supplier, boolean flag, boolean flag1, long i, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, 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 = new com.destroystokyo.paper.PaperWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName(), this.spigotConfig); // Paper
+ this.sakuraConfig = new me.samsuik.sakura.configuration.SakuraWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Sakura
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 3d07ef0cbd57d54d131cdc766dd55d210d67fb4c..62ee1aebdea1acb3c213d768f82f9131141343ad 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -956,6 +956,7 @@ public final class CraftServer implements Server {
org.spigotmc.SpigotConfig.init((File) console.options.valueOf("spigot-settings")); // Spigot
com.destroystokyo.paper.PaperConfig.init((File) console.options.valueOf("paper-settings")); // Paper
+ me.samsuik.sakura.configuration.SakuraGlobalConfig.get().setup((File) console.options.valueOf("sakura-settings")); // Sakura
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))
@@ -972,6 +973,7 @@ public final class CraftServer implements Server {
}
world.spigotConfig.init(); // Spigot
world.paperConfig.init(); // Paper
+ world.sakuraConfig.init(); // Sakura
}
Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper
@@ -987,6 +989,7 @@ public final class CraftServer implements Server {
this.reloadData();
org.spigotmc.SpigotConfig.registerCommands(); // Spigot
com.destroystokyo.paper.PaperConfig.registerCommands(); // Paper
+ me.samsuik.sakura.command.SakuraCommands.registerCommands(this.console); // Sakura
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 fbe68bd4739d9a0e7d9bc4c3d5ba8ecfd2d13954..c779e4a4df4c954167a7445f2ac29da232e04f20 100644
--- a/src/main/java/org/bukkit/craftbukkit/Main.java
+++ b/src/main/java/org/bukkit/craftbukkit/Main.java
@@ -153,6 +153,14 @@ public class Main {
.describedAs("Jar file");
// Paper end
+ // Sakura start
+ acceptsAll(asList("sakura", "sakura-settings"), "File for sakura settings")
+ .withRequiredArg()
+ .ofType(File.class)
+ .defaultsTo(new File("sakura.yml"))
+ .describedAs("Yml file");
+ // Sakura end
+
// Paper start
acceptsAll(asList("server-name"), "Name of the server")
.withRequiredArg()