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..0fda065100c17ee00060df16467c0f7f98984ea7 --- /dev/null +++ b/src/main/java/io/papermc/paper/world/flag/FeatureDependant.java @@ -0,0 +1,27 @@ +package io.papermc.paper.world.flag; + +import java.util.Set; +import org.bukkit.FeatureFlag; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Unmodifiable; + +/** + * 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. + */ +@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 @NonNull 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..f64cbee35fd3c6fe7b5f7391f6d118166d26dc18 --- /dev/null +++ b/src/main/java/io/papermc/paper/world/flag/FeatureFlagProvider.java @@ -0,0 +1,19 @@ +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; + +@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..c4432ee20872eeb127852159cf89ed00809fb00e --- /dev/null +++ b/src/main/java/io/papermc/paper/world/flag/FeatureFlagSetHolder.java @@ -0,0 +1,31 @@ +package io.papermc.paper.world.flag; + +import java.util.Set; +import org.bukkit.FeatureFlag; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Unmodifiable; + +/** + * Implemented by types that hold {@link FeatureFlag FeatureFlags} like + * {@link org.bukkit.generator.WorldInfo} and {@link org.bukkit.RegionAccessor}. + */ +@ApiStatus.NonExtendable +public interface FeatureFlagSetHolder { + + /** + * Checks if this is enabled based on the loaded feature flags. + * + * @return true if enabled + */ + default boolean isEnabled(final @NonNull FeatureDependant featureDependant) { + return this.getFeatureFlags().containsAll(featureDependant.requiredFeatures()); + } + + /** + * Get all {@link FeatureFlag FeatureFlags} enabled in this world. + * + * @return all enabled {@link FeatureFlag FeatureFlags} + */ + @Unmodifiable @NonNull Set getFeatureFlags(); +} diff --git a/src/main/java/org/bukkit/FeatureFlag.java b/src/main/java/org/bukkit/FeatureFlag.java index 026b1832bcd163ab89668c991bf002e608e36aef..b5e87480b6a1d064547e4e608f3d402825931a00 100644 --- a/src/main/java/org/bukkit/FeatureFlag.java +++ b/src/main/java/org/bukkit/FeatureFlag.java @@ -1,37 +1,56 @@ 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"); + /** + * The {@code bundle} feature flag. + */ @ApiStatus.Experimental // Paper - add missing annotation - public static final FeatureFlag BUNDLE = Bukkit.getUnsafe().getFeatureFlag(NamespacedKey.minecraft("bundle")); + FeatureFlag BUNDLE = create("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"); + + @Deprecated(since = "1.20") + FeatureFlag UPDATE_1_20 = deprecated("update_1_20"); + + @Deprecated(since = "1.21") + FeatureFlag UPDATE_121 = deprecated("update_1_21"); /** - * AVAILABLE BETWEEN VERSIONS: 1.20.5 - 1.20.6 - * - * @deprecated not available since 1.21 + * An index of all feature flags. */ - @Deprecated - public static final FeatureFlag UPDATE_121 = Bukkit.getUnsafe().getFeatureFlag(NamespacedKey.minecraft("update_1_21")); + 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..a33517dbd69021fde8246836720b6247800ee43c --- /dev/null +++ b/src/main/java/org/bukkit/FeatureFlagImpl.java @@ -0,0 +1,29 @@ +package org.bukkit; + +import java.util.HashSet; +import java.util.Set; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +@DefaultQualifier(NonNull.class) +record FeatureFlagImpl(NamespacedKey key) implements FeatureFlag { + + static final Set ALL_FLAGS = new HashSet<>(); + + @Override + public NamespacedKey getKey() { + return this.key; + } + + @ApiStatus.Internal + @DefaultQualifier(NonNull.class) + 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 330e3013eda204aa9b33d5e1c3104e0b595abdbc..c80e0ef587a001ee6de3f5c182cc9696d58bafeb 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java @@ -114,8 +114,7 @@ public interface UnsafeValues { 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 0fa0fa4aaf55710030a2220dee98e11764d8d27a..e85bdd92466ee9bfcf8a82614ad09c1e2963731b 100644 --- a/src/main/java/org/bukkit/block/BlockType.java +++ b/src/main/java/org/bukkit/block/BlockType.java @@ -125,7 +125,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 @@ -3486,7 +3486,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 ba605ad75d4ed920c0dc4527529998041a58676b..a78e1c431a6ea46ba7c44880e25a871f473bef41 100644 --- a/src/main/java/org/bukkit/entity/EntityType.java +++ b/src/main/java/org/bukkit/entity/EntityType.java @@ -23,7 +23,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 0168f0a14a3e899e84c5e36963ff79950ab580fb..5b4449f63595193efcd1329b9df0558cfaa2c7b2 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 @@ -2306,7 +2306,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 29dfad691cbdd09d043f6171defc8a7fabbc2b75..5ddc24df90c3a4b9f92a49437e1aa90e3961daf3 100644 --- a/src/main/java/org/bukkit/inventory/MenuType.java +++ b/src/main/java/org/bukkit/inventory/MenuType.java @@ -23,7 +23,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 10fa51d116b40450b51af9110d5637f3505ebf65..647452f16e4bf7e5a61520123e62d9408ec9adfb 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"),