diff --git a/paper-api/src/main/java/io/papermc/paper/world/flag/FeatureDependant.java b/paper-api/src/main/java/io/papermc/paper/world/flag/FeatureDependant.java new file mode 100644 index 0000000000..f5633319c6 --- /dev/null +++ b/paper-api/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/paper-api/src/main/java/io/papermc/paper/world/flag/FeatureFlagProvider.java b/paper-api/src/main/java/io/papermc/paper/world/flag/FeatureFlagProvider.java new file mode 100644 index 0000000000..388e70628f --- /dev/null +++ b/paper-api/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/paper-api/src/main/java/io/papermc/paper/world/flag/FeatureFlagSetHolder.java b/paper-api/src/main/java/io/papermc/paper/world/flag/FeatureFlagSetHolder.java new file mode 100644 index 0000000000..0955699df6 --- /dev/null +++ b/paper-api/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/paper-api/src/main/java/org/bukkit/FeatureFlag.java b/paper-api/src/main/java/org/bukkit/FeatureFlag.java index dd157b650a..a1e093c40c 100644 --- a/paper-api/src/main/java/org/bukkit/FeatureFlag.java +++ b/paper-api/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,18 +26,19 @@ public interface FeatureFlag extends Keyed { * @deprecated not available since 1.21.2 */ @Deprecated(since = "1.21.2") - 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(since = "1.20.2") - public static final FeatureFlag UPDATE_1_20 = Bukkit.getUnsafe().getFeatureFlag(NamespacedKey.minecraft("update_1_20")); + @Deprecated(since = "1.20") + FeatureFlag UPDATE_1_20 = deprecated("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 @@ -38,7 +46,7 @@ public interface FeatureFlag extends Keyed { * @deprecated not available since 1.21 */ @Deprecated(since = "1.21") - public static final FeatureFlag UPDATE_121 = Bukkit.getUnsafe().getFeatureFlag(NamespacedKey.minecraft("update_1_21")); + FeatureFlag UPDATE_121 = deprecated("update_1_21"); /** * AVAILABLE BETWEEN VERSIONS: 1.21.2 - 1.21.3 @@ -46,12 +54,28 @@ public interface FeatureFlag extends Keyed { * @deprecated not available since 1.21.4 */ @Deprecated(since = "1.21.4") - public static final FeatureFlag WINTER_DROP = Bukkit.getUnsafe().getFeatureFlag(NamespacedKey.minecraft("winter_drop")); + FeatureFlag WINTER_DROP = deprecated("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/paper-api/src/main/java/org/bukkit/FeatureFlagImpl.java b/paper-api/src/main/java/org/bukkit/FeatureFlagImpl.java new file mode 100644 index 0000000000..5a43f65f63 --- /dev/null +++ b/paper-api/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/paper-api/src/main/java/org/bukkit/RegionAccessor.java b/paper-api/src/main/java/org/bukkit/RegionAccessor.java index eb33e8e671..05ecf3cb38 100644 --- a/paper-api/src/main/java/org/bukkit/RegionAccessor.java +++ b/paper-api/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/paper-api/src/main/java/org/bukkit/UnsafeValues.java b/paper-api/src/main/java/org/bukkit/UnsafeValues.java index 6b1cb9c49b..98fe2763dc 100644 --- a/paper-api/src/main/java/org/bukkit/UnsafeValues.java +++ b/paper-api/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/paper-api/src/main/java/org/bukkit/block/BlockType.java b/paper-api/src/main/java/org/bukkit/block/BlockType.java index a2376f08c8..10fb9624ff 100644 --- a/paper-api/src/main/java/org/bukkit/block/BlockType.java +++ b/paper-api/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 @@ -3604,7 +3604,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/paper-api/src/main/java/org/bukkit/entity/EntityType.java b/paper-api/src/main/java/org/bukkit/entity/EntityType.java index 6bc514af55..0251ec12e8 100644 --- a/paper-api/src/main/java/org/bukkit/entity/EntityType.java +++ b/paper-api/src/main/java/org/bukkit/entity/EntityType.java @@ -43,7 +43,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/paper-api/src/main/java/org/bukkit/generator/WorldInfo.java b/paper-api/src/main/java/org/bukkit/generator/WorldInfo.java index 5067f13714..e0cb282541 100644 --- a/paper-api/src/main/java/org/bukkit/generator/WorldInfo.java +++ b/paper-api/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/paper-api/src/main/java/org/bukkit/inventory/ItemType.java b/paper-api/src/main/java/org/bukkit/inventory/ItemType.java index c65ba431cd..5fbe84dc41 100644 --- a/paper-api/src/main/java/org/bukkit/inventory/ItemType.java +++ b/paper-api/src/main/java/org/bukkit/inventory/ItemType.java @@ -47,7 +47,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 @@ -2412,7 +2412,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/paper-api/src/main/java/org/bukkit/inventory/MenuType.java b/paper-api/src/main/java/org/bukkit/inventory/MenuType.java index 34d45a105d..529143c900 100644 --- a/paper-api/src/main/java/org/bukkit/inventory/MenuType.java +++ b/paper-api/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/paper-api/src/main/java/org/bukkit/potion/PotionEffectType.java b/paper-api/src/main/java/org/bukkit/potion/PotionEffectType.java index 5f32728fad..e4cfdc80c9 100644 --- a/paper-api/src/main/java/org/bukkit/potion/PotionEffectType.java +++ b/paper-api/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/paper-api/src/main/java/org/bukkit/potion/PotionType.java b/paper-api/src/main/java/org/bukkit/potion/PotionType.java index ca79f97be6..912eb2f48c 100644 --- a/paper-api/src/main/java/org/bukkit/potion/PotionType.java +++ b/paper-api/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"),