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 218bf89fd7583d6db9f64754c4db8fcce5415bdb..df0da18b38f382a452b1e1489a4f2c332f2f1311 100644 --- a/src/main/java/io/papermc/paper/configuration/Configurations.java +++ b/src/main/java/io/papermc/paper/configuration/Configurations.java @@ -93,7 +93,7 @@ public abstract class Configurations { }; } - static CheckedFunction reloader(Class type, T instance) { + public static CheckedFunction reloader(Class type, T instance) { // Sakura - public return node -> { ObjectMapper.Factory factory = (ObjectMapper.Factory) Objects.requireNonNull(node.options().serializers().get(type)); ObjectMapper.Mutable mutable = (ObjectMapper.Mutable) factory.get(type); @@ -227,7 +227,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 fa1c0aee8c3a4d0868482cf5c703bbfd08e09874..9ea2e60095526e63a1f4a0087cfd59067bb92c7e 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 final class InnerClassInstanceFactory implements FieldDiscoverer.MutableInstanceFactory> { // Sakura private final InnerClassInstanceSupplier instanceSupplier; private final FieldDiscoverer.MutableInstanceFactory> fallback; private final AnnotatedType targetType; - InnerClassInstanceFactory(final InnerClassInstanceSupplier instanceSupplier, final FieldDiscoverer.MutableInstanceFactory> fallback, final AnnotatedType targetType) { + public InnerClassInstanceFactory(final InnerClassInstanceSupplier instanceSupplier, final FieldDiscoverer.MutableInstanceFactory> fallback, final AnnotatedType targetType) { // Sakura this.instanceSupplier = instanceSupplier; this.fallback = fallback; this.targetType = targetType; diff --git a/src/main/java/io/papermc/paper/configuration/mapping/InnerClassInstanceSupplier.java b/src/main/java/io/papermc/paper/configuration/mapping/InnerClassInstanceSupplier.java index 8d8bc050441c02cf65dfcb6400978363d6b8ef10..5872095a811452037cc0772ba2a31bf9a795cc82 100644 --- a/src/main/java/io/papermc/paper/configuration/mapping/InnerClassInstanceSupplier.java +++ b/src/main/java/io/papermc/paper/configuration/mapping/InnerClassInstanceSupplier.java @@ -19,7 +19,7 @@ import static io.leangen.geantyref.GenericTypeReflector.erase; * {@link ConfigurationPart}. Only 1 instance of each {@link ConfigurationPart} should be present for each instance * of the field discoverer this is used in. */ -final class InnerClassInstanceSupplier implements CheckedFunction, SerializationException> { +public final class InnerClassInstanceSupplier implements CheckedFunction, SerializationException> { // Sakua - :< private final Map, Object> instanceMap = new HashMap<>(); private final Map, Object> initialOverrides; @@ -27,7 +27,7 @@ final class InnerClassInstanceSupplier implements CheckedFunction, Object> initialOverrides) { + public InnerClassInstanceSupplier(final Map, Object> initialOverrides) { // Sakura this.initialOverrides = initialOverrides; } 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 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 tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException { + var completions = new ArrayList(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(""" + . + | 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) { + 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 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..3b08454cf7411d12bb33225df59800bd73312123 --- /dev/null +++ b/src/main/java/me/samsuik/sakura/command/SakuraCommands.java @@ -0,0 +1,26 @@ +package me.samsuik.sakura.command; + +import io.papermc.paper.command.PaperPluginsCommand; +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(final MinecraftServer server) { + COMMANDS.forEach((s, command) -> { + server.server.getCommandMap().register(s, "sakura", command); + }); + server.server.getCommandMap().register("bukkit", new PaperPluginsCommand()); + } + +} 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..75febc3f40910a27a9fc651dac9697da48338cc1 --- /dev/null +++ b/src/main/java/me/samsuik/sakura/command/subcommands/ConfigCommand.java @@ -0,0 +1,45 @@ +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 org.jetbrains.annotations.NotNull; + +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(); + server.sakuraConfigurations.reloadConfigs(server); + server.server.reloadCount++; + + Command.broadcastCommandMessage(sender, text("Sakura config reload complete.", GREEN)); + } + + @NotNull + @Override + public List tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException { + return Collections.emptyList(); + } + +} 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..f8770f97e3ad6c2746bc436b2b2c895cecd43996 --- /dev/null +++ b/src/main/java/me/samsuik/sakura/configuration/SakuraConfigurations.java @@ -0,0 +1,253 @@ +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.V3_RenameKnockback; +import me.samsuik.sakura.configuration.transformation.world.V2_VerticalKnockbackUseDefault; +import me.samsuik.sakura.configuration.transformation.world.V4_RenameNonStrictMergeLevel; +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.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) throws ConfigurateException { + final ConfigurationTransformation.VersionedBuilder versionedBuilder = Transformations.versionedBuilder(); + V2_VerticalKnockbackUseDefault.apply(versionedBuilder); + V3_RenameKnockback.apply(versionedBuilder); + V4_RenameNonStrictMergeLevel.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..12cbf3e424a0d3218d7e8c5ea37c32ff3bad4e98 --- /dev/null +++ b/src/main/java/me/samsuik/sakura/configuration/WorldConfiguration.java @@ -0,0 +1,204 @@ +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 = 4; // (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 Tnt tnt = new Tnt(); + public class Tnt extends ConfigurationPart { + public boolean loadsChunks; + public boolean forcePositionUpdates; + } + + public Sand sand = new Sand(); + public class Sand extends ConfigurationPart { + public boolean loadsChunks; + 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 (!preventAgainstBorder || !io.papermc.paper.util.CollisionUtil.isCollidingWithBorder(entity.level().getWorldBorder(), entity.getBoundingBox().inflate(0.01))) + && (!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 = true; + 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 allowNonTntBreakingDurableBlocks = false; + public boolean destroyWaterloggedBlocks = false; + public boolean explodeLava = false; + public boolean consistentRadius = false; + public boolean explosionsHurtPlayers = true; + public boolean explosionsDropItems = true; + } + + 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 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 = 1.0; + @Comment("Delay between extra knockback hits in milliseconds") + public IntOr.Default knockbackDelay = IntOr.Default.USE_DEFAULT; + } + + @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 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/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 e4ef4306d93cb81868468b78b98fc4c6b7633103..ca9013a7141a4e6b5b45651961dd977929a0756b 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -306,6 +306,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop minecraftserver.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig, minecraftserver.registryAccess())), 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())), () -> minecraftserver.sakuraConfigurations.createWorldConfig(me.samsuik.sakura.configuration.SakuraConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), minecraftserver.registryAccess())), executor); // Sakura // 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 ca89d1593bf1b46c79a882db528cbca1359dc9d4..a0a68ff09689d457f3c6e17f3e5d6d595f84684a 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 { return this.paperConfig; } // Paper end - add paper world config + // Sakura start + private final me.samsuik.sakura.configuration.WorldConfiguration sakuraConfig; + public final me.samsuik.sakura.configuration.WorldConfiguration sakuraConfig() { + return this.sakuraConfig; + } + // Sakura end public final com.destroystokyo.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray public final co.aikar.timings.WorldTimingsHandler timings; // Paper @@ -216,9 +222,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable { public abstract ResourceKey getTypeKey(); - 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; Async-Anti-Xray: Pass executor + protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, RegistryAccess iregistrycustom, Holder holder, Supplier supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function paperWorldConfigCreator, Supplier sakuraWorldConfigCreator, java.util.concurrent.Executor executor) { // Sakura // Paper - create paper world config; Async-Anti-Xray: Pass executor this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config + this.sakuraConfig = sakuraWorldConfigCreator.get(); // 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 af2c7758373ca3ecd53ca185f6730c284e964b26..870da24cb911f1cad20c79cc1bfbf3dc69ed3b83 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -1054,6 +1054,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)) @@ -1084,6 +1085,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 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 8d626fadcd4743b6472a2954d2b1b2ec89669814..7107b54327f76bbd5265898f09e166423fee96e0 100644 --- a/src/main/java/org/bukkit/craftbukkit/Main.java +++ b/src/main/java/org/bukkit/craftbukkit/Main.java @@ -174,6 +174,14 @@ public class Main { .describedAs("Jar file"); // Paper end + // Sakura start + 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 + // Paper start acceptsAll(asList("server-name"), "Name of the server") .withRequiredArg()