From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Jake Potrebic Date: Thu, 9 Mar 2023 11:24:43 -0800 Subject: [PATCH] Add FeatureFlag API diff --git a/src/main/java/io/papermc/paper/world/flag/FeatureDependant.java b/src/main/java/io/papermc/paper/world/flag/FeatureDependant.java new file mode 100644 index 0000000000000000000000000000000000000000..f5633319c64bf19cf07e2b9a1cac2dd1d11ab70b --- /dev/null +++ b/src/main/java/io/papermc/paper/world/flag/FeatureDependant.java @@ -0,0 +1,28 @@ +package io.papermc.paper.world.flag; + +import java.util.Set; +import org.bukkit.FeatureFlag; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Unmodifiable; +import org.jspecify.annotations.NullMarked; + +/** + * Implemented by types in built-in registries that are controlled by {@link FeatureFlag FeatureFlags}. + * Types in data-driven registries that are controlled by feature flags just will not exist in that registry. + * @apiNote When a type that currently implements this interface transitions to being data-drive, this + * interface will be removed from that type in the following major version. + */ +@NullMarked +@ApiStatus.NonExtendable +public interface FeatureDependant { + + /** + * Gets the set of required feature flags for this + * to be enabled. + * + * @return the immutable set of feature flags + */ + default @Unmodifiable Set requiredFeatures() { + return FeatureFlagProvider.provider().requiredFeatures(this); + } +} diff --git a/src/main/java/io/papermc/paper/world/flag/FeatureFlagProvider.java b/src/main/java/io/papermc/paper/world/flag/FeatureFlagProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..388e70628f88d8ee0749a43f82b228453e25d5d0 --- /dev/null +++ b/src/main/java/io/papermc/paper/world/flag/FeatureFlagProvider.java @@ -0,0 +1,21 @@ +package io.papermc.paper.world.flag; + +import java.util.Optional; +import java.util.ServiceLoader; +import java.util.Set; +import org.bukkit.FeatureFlag; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; + +@NullMarked +@ApiStatus.Internal +interface FeatureFlagProvider { + + Optional PROVIDER = ServiceLoader.load(FeatureFlagProvider.class).findFirst(); + + static FeatureFlagProvider provider() { + return PROVIDER.orElseThrow(); + } + + Set requiredFeatures(FeatureDependant dependant); +} diff --git a/src/main/java/io/papermc/paper/world/flag/FeatureFlagSetHolder.java b/src/main/java/io/papermc/paper/world/flag/FeatureFlagSetHolder.java new file mode 100644 index 0000000000000000000000000000000000000000..0955699df65ccbb8711cfa48f0b34d5a8805f1af --- /dev/null +++ b/src/main/java/io/papermc/paper/world/flag/FeatureFlagSetHolder.java @@ -0,0 +1,32 @@ +package io.papermc.paper.world.flag; + +import java.util.Set; +import org.bukkit.FeatureFlag; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Unmodifiable; +import org.jspecify.annotations.NullMarked; + +/** + * Implemented by types that hold {@link FeatureFlag FeatureFlags} like + * {@link org.bukkit.generator.WorldInfo} and {@link org.bukkit.RegionAccessor}. + */ +@NullMarked +@ApiStatus.NonExtendable +public interface FeatureFlagSetHolder { + + /** + * Checks if this is enabled based on the loaded feature flags. + * + * @return true if enabled + */ + default boolean isEnabled(final FeatureDependant featureDependant) { + return this.getFeatureFlags().containsAll(featureDependant.requiredFeatures()); + } + + /** + * Get all {@link FeatureFlag FeatureFlags} enabled in this world. + * + * @return all enabled {@link FeatureFlag FeatureFlags} + */ + @Unmodifiable Set getFeatureFlags(); +} diff --git a/src/main/java/org/bukkit/FeatureFlag.java b/src/main/java/org/bukkit/FeatureFlag.java index 20eb27ee041f77f295eb271f878c524ce5592251..0acea3e8d639c252b44cd5da5e3584aa6e6c9b5c 100644 --- a/src/main/java/org/bukkit/FeatureFlag.java +++ b/src/main/java/org/bukkit/FeatureFlag.java @@ -1,17 +1,24 @@ package org.bukkit; +// Paper start - overhaul FeatureFlag API +import com.google.common.base.Preconditions; +import java.util.List; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.util.Index; +import org.intellij.lang.annotations.Subst; +// Paper end - overhaul FeatureFlag API import org.jetbrains.annotations.ApiStatus; /** - * This represents a Feature Flag for a World. - *
- * Flags which are unavailable in the current version will be null and/or - * removed. + * This represents a Feature Flag for a {@link io.papermc.paper.world.flag.FeatureFlagSetHolder}. */ -@ApiStatus.Experimental public interface FeatureFlag extends Keyed { - public static final FeatureFlag VANILLA = Bukkit.getUnsafe().getFeatureFlag(NamespacedKey.minecraft("vanilla")); + // Paper start - overhaul FeatureFlag API + /** + * The {@code vanilla} feature flag. + */ + FeatureFlag VANILLA = create("vanilla"); /** * AVAILABLE BETWEEN VERSIONS: 1.19.3 - 1.21.1 @@ -19,34 +26,43 @@ public interface FeatureFlag extends Keyed { * @deprecated not available since 1.21.2 */ @Deprecated - public static final FeatureFlag BUNDLE = Bukkit.getUnsafe().getFeatureFlag(NamespacedKey.minecraft("bundle")); + FeatureFlag BUNDLE = deprecated("bundle"); /** - * AVAILABLE BETWEEN VERSIONS: 1.19 - 1.19.4 - * - * @deprecated not available since 1.20 + * The {@code trade_rebalance} feature flag. */ - @Deprecated - public static final FeatureFlag UPDATE_1_20 = Bukkit.getUnsafe().getFeatureFlag(NamespacedKey.minecraft("update_1_20")); - @ApiStatus.Experimental // Paper - add missing annotation - public static final FeatureFlag TRADE_REBALANCE = Bukkit.getUnsafe().getFeatureFlag(NamespacedKey.minecraft("trade_rebalance")); + FeatureFlag TRADE_REBALANCE = create("trade_rebalance"); - /** - * AVAILABLE BETWEEN VERSIONS: 1.20.5 - 1.20.6 - * - * @deprecated not available since 1.21 - */ - @Deprecated - public static final FeatureFlag UPDATE_121 = Bukkit.getUnsafe().getFeatureFlag(NamespacedKey.minecraft("update_1_21")); + @Deprecated(since = "1.20") + FeatureFlag UPDATE_1_20 = deprecated("update_1_20"); + + @Deprecated(since = "1.21") + FeatureFlag UPDATE_121 = deprecated("update_1_21"); @ApiStatus.Experimental // Paper - add missing annotation - public static final FeatureFlag WINTER_DROP = Bukkit.getUnsafe().getFeatureFlag(NamespacedKey.minecraft("winter_drop")); + FeatureFlag WINTER_DROP = create("winter_drop"); @ApiStatus.Experimental // Paper - add missing annotation - public static final FeatureFlag REDSTONE_EXPERIMENTS = Bukkit.getUnsafe().getFeatureFlag(NamespacedKey.minecraft("redstone_experiments")); + FeatureFlag REDSTONE_EXPERIMENTS = create("redstone_experiments"); @ApiStatus.Experimental // Paper - add missing annotation - public static final FeatureFlag MINECART_IMPROVEMENTS = Bukkit.getUnsafe().getFeatureFlag(NamespacedKey.minecraft("minecart_improvements")); + FeatureFlag MINECART_IMPROVEMENTS = create("minecart_improvements"); + + /** + * An index of all feature flags. + */ + Index ALL_FLAGS = Index.create(FeatureFlag::key, List.copyOf(FeatureFlagImpl.ALL_FLAGS)); + + private static FeatureFlag create(@Subst("vanilla") final String name) { + final FeatureFlag flag = new FeatureFlagImpl(NamespacedKey.minecraft(name)); + Preconditions.checkState(FeatureFlagImpl.ALL_FLAGS.add(flag), "Tried to add duplicate feature flag: " + name); + return flag; + } + + private static FeatureFlag deprecated(@Subst("vanilla") final String name) { + return new FeatureFlagImpl.Deprecated(NamespacedKey.minecraft(name)); + } + // Paper end - overhaul FeatureFlag API } diff --git a/src/main/java/org/bukkit/FeatureFlagImpl.java b/src/main/java/org/bukkit/FeatureFlagImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..5a43f65f633dc9a1a0ba7dba0c15488375427444 --- /dev/null +++ b/src/main/java/org/bukkit/FeatureFlagImpl.java @@ -0,0 +1,27 @@ +package org.bukkit; + +import java.util.HashSet; +import java.util.Set; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; + +@ApiStatus.Internal +@NullMarked +record FeatureFlagImpl(NamespacedKey key) implements FeatureFlag { + + static final Set ALL_FLAGS = new HashSet<>(); + + @Override + public NamespacedKey getKey() { + return this.key; + } + + @ApiStatus.Internal + record Deprecated(NamespacedKey key) implements FeatureFlag { + + @Override + public NamespacedKey getKey() { + return this.key; + } + } +} diff --git a/src/main/java/org/bukkit/RegionAccessor.java b/src/main/java/org/bukkit/RegionAccessor.java index eb33e8e671972aa308ad75a7ce9aa9ac526f470f..05ecf3cb38ff42c8b52405d900197e6bf47dd1e6 100644 --- a/src/main/java/org/bukkit/RegionAccessor.java +++ b/src/main/java/org/bukkit/RegionAccessor.java @@ -18,7 +18,7 @@ import org.jetbrains.annotations.Nullable; * A RegionAccessor gives access to getting, modifying and spawning {@link Biome}, {@link BlockState} and {@link Entity}, * as well as generating some basic structures. */ -public interface RegionAccessor extends Keyed { // Paper +public interface RegionAccessor extends Keyed, io.papermc.paper.world.flag.FeatureFlagSetHolder { // Paper - feature flag API /** * Gets the {@link Biome} at the given {@link Location}. diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java index 307439827b401acb96f0df1cf4ef31835bd1d513..e8dc8a6abebf6c31cb095ca3646eb4909e42f105 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java @@ -111,8 +111,7 @@ public interface UnsafeValues { @Deprecated(since = "1.21.3", forRemoval = true) String getTranslationKey(Attribute attribute); - @Nullable - FeatureFlag getFeatureFlag(@NotNull NamespacedKey key); + // Paper - replace with better system /** * Do not use, method will get removed, and the plugin won't run diff --git a/src/main/java/org/bukkit/block/BlockType.java b/src/main/java/org/bukkit/block/BlockType.java index c080c2a3323d19cb3d549aa0fe6c164666d7da75..ed534fe4983873a2d5f623f0d9d5e3ce254615eb 100644 --- a/src/main/java/org/bukkit/block/BlockType.java +++ b/src/main/java/org/bukkit/block/BlockType.java @@ -129,7 +129,7 @@ import org.jetbrains.annotations.Nullable; * changes may occur. Do not use this API in plugins. */ @ApiStatus.Internal -public interface BlockType extends Keyed, Translatable, net.kyori.adventure.translation.Translatable { // Paper - add translatable +public interface BlockType extends Keyed, Translatable, net.kyori.adventure.translation.Translatable, io.papermc.paper.world.flag.FeatureDependant { // Paper - add translatable & feature flag API /** * Typed represents a subtype of {@link BlockType}s that have a known block @@ -3629,7 +3629,9 @@ public interface BlockType extends Keyed, Translatable, net.kyori.adventure.tran * * @param world the world to check * @return true if this BlockType can be used in this World. + * @deprecated Use {@link io.papermc.paper.world.flag.FeatureFlagSetHolder#isEnabled(io.papermc.paper.world.flag.FeatureDependant)} */ + @Deprecated(forRemoval = true, since = "1.21.1") // Paper boolean isEnabledByFeature(@NotNull World world); /** diff --git a/src/main/java/org/bukkit/entity/EntityType.java b/src/main/java/org/bukkit/entity/EntityType.java index 9e115cec6232b22760e91ade671f81461c0c0364..c1aa59bab82aee8fd42f7149c520b45660b29082 100644 --- a/src/main/java/org/bukkit/entity/EntityType.java +++ b/src/main/java/org/bukkit/entity/EntityType.java @@ -45,7 +45,7 @@ import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public enum EntityType implements Keyed, Translatable, net.kyori.adventure.translation.Translatable { // Paper - translatable +public enum EntityType implements Keyed, Translatable, net.kyori.adventure.translation.Translatable, io.papermc.paper.world.flag.FeatureDependant { // Paper - translatable // These strings MUST match the strings in nms.EntityTypes and are case sensitive. /** diff --git a/src/main/java/org/bukkit/generator/WorldInfo.java b/src/main/java/org/bukkit/generator/WorldInfo.java index 5067f1371433cccd3287af7f03e152f2c3c1ece3..e0cb282541548ac3bd24cce86b3413f5748ea8bc 100644 --- a/src/main/java/org/bukkit/generator/WorldInfo.java +++ b/src/main/java/org/bukkit/generator/WorldInfo.java @@ -7,7 +7,7 @@ import org.jetbrains.annotations.NotNull; /** * Holds various information of a World */ -public interface WorldInfo { +public interface WorldInfo extends io.papermc.paper.world.flag.FeatureFlagSetHolder { // Paper - feature flag API /** * Gets the unique name of this world diff --git a/src/main/java/org/bukkit/inventory/ItemType.java b/src/main/java/org/bukkit/inventory/ItemType.java index ef3a30d5cca29c7a7c546791be3c333e63e425f4..72803c00e4af576f286d2af34bf300ee554a7f3c 100644 --- a/src/main/java/org/bukkit/inventory/ItemType.java +++ b/src/main/java/org/bukkit/inventory/ItemType.java @@ -48,7 +48,7 @@ import org.jetbrains.annotations.Nullable; * changes may occur. Do not use this API in plugins. */ @ApiStatus.Experimental // Paper - already required for registry builders -public interface ItemType extends Keyed, Translatable, net.kyori.adventure.translation.Translatable { // Paper - add Translatable +public interface ItemType extends Keyed, Translatable, net.kyori.adventure.translation.Translatable, io.papermc.paper.world.flag.FeatureDependant { // Paper - add Translatable & feature flag API /** * Typed represents a subtype of {@link ItemType}s that have a known item meta type @@ -2451,7 +2451,9 @@ public interface ItemType extends Keyed, Translatable, net.kyori.adventure.trans * * @param world the world to check * @return true if this ItemType can be used in this World. + * @deprecated use {@link io.papermc.paper.world.flag.FeatureFlagSetHolder#isEnabled(io.papermc.paper.world.flag.FeatureDependant)} */ + @Deprecated(forRemoval = true, since = "1.21.1") // Paper boolean isEnabledByFeature(@NotNull World world); /** diff --git a/src/main/java/org/bukkit/inventory/MenuType.java b/src/main/java/org/bukkit/inventory/MenuType.java index 34d45a105da72481a7c4f2e3831f5d2a676c91c6..529143c9007d6da4f671576f9934933f2e5d6f23 100644 --- a/src/main/java/org/bukkit/inventory/MenuType.java +++ b/src/main/java/org/bukkit/inventory/MenuType.java @@ -22,7 +22,7 @@ import org.jetbrains.annotations.NotNull; * created and viewed by the player. */ @ApiStatus.Experimental -public interface MenuType extends Keyed { +public interface MenuType extends Keyed, io.papermc.paper.world.flag.FeatureDependant { // Paper - make FeatureDependant /** * A MenuType which represents a chest with 1 row. diff --git a/src/main/java/org/bukkit/potion/PotionEffectType.java b/src/main/java/org/bukkit/potion/PotionEffectType.java index d02bb37768404422969d8b93e009960d0a693a93..294ba70f1e446ec8d502e5c14f82ae52547aeb21 100644 --- a/src/main/java/org/bukkit/potion/PotionEffectType.java +++ b/src/main/java/org/bukkit/potion/PotionEffectType.java @@ -17,7 +17,7 @@ import org.jetbrains.annotations.Nullable; /** * Represents a type of potion and its effect on an entity. */ -public abstract class PotionEffectType implements Keyed, Translatable, net.kyori.adventure.translation.Translatable { // Paper - implement Translatable +public abstract class PotionEffectType implements Keyed, Translatable, net.kyori.adventure.translation.Translatable, io.papermc.paper.world.flag.FeatureDependant { // Paper - implement Translatable & feature flag API private static final BiMap ID_MAP = HashBiMap.create(); /** diff --git a/src/main/java/org/bukkit/potion/PotionType.java b/src/main/java/org/bukkit/potion/PotionType.java index 453c626092caa734a0af5800699f299fc6fd0e43..c82cc9d9db5cb0e0e3c02f6a9564fc935ee594aa 100644 --- a/src/main/java/org/bukkit/potion/PotionType.java +++ b/src/main/java/org/bukkit/potion/PotionType.java @@ -14,7 +14,7 @@ import org.jetbrains.annotations.Nullable; * This enum reflects and matches each potion state that can be obtained from * the Creative mode inventory */ -public enum PotionType implements Keyed { +public enum PotionType implements Keyed, io.papermc.paper.world.flag.FeatureDependant { // Paper - feature flag API WATER("water"), MUNDANE("mundane"), THICK("thick"),