Add FeatureFlag API

This commit is contained in:
Jake Potrebic 2024-05-29 19:50:21 -07:00
parent e29ea45639
commit 90bc5f1e0b
15 changed files with 197 additions and 67 deletions

View file

@ -0,0 +1,17 @@
package io.papermc.paper.world.flag;
import java.util.Set;
import net.minecraft.world.flag.FeatureElement;
import org.bukkit.FeatureFlag;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.jetbrains.annotations.Unmodifiable;
public interface PaperFeatureDependent extends FeatureDependant {
<M extends FeatureElement> M getHandle();
@Override
default @Unmodifiable @NonNull Set<FeatureFlag> requiredFeatures() {
return PaperFeatureFlagProviderImpl.fromNms(this.getHandle().requiredFeatures());
}
}

View file

@ -0,0 +1,54 @@
package io.papermc.paper.world.flag;
import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import net.minecraft.world.flag.FeatureElement;
import net.minecraft.world.flag.FeatureFlagSet;
import net.minecraft.world.flag.FeatureFlags;
import org.bukkit.FeatureFlag;
import org.bukkit.craftbukkit.entity.CraftEntityType;
import org.bukkit.craftbukkit.entity.CraftEntityTypes;
import org.bukkit.craftbukkit.potion.CraftPotionType;
import org.bukkit.craftbukkit.util.CraftMagicNumbers;
import org.bukkit.entity.EntityType;
import org.bukkit.potion.PotionType;
public class PaperFeatureFlagProviderImpl implements FeatureFlagProvider {
public static final BiMap<FeatureFlag, net.minecraft.world.flag.FeatureFlag> FLAGS = ImmutableBiMap.of(
FeatureFlag.VANILLA, FeatureFlags.VANILLA,
FeatureFlag.TRADE_REBALANCE, FeatureFlags.TRADE_REBALANCE,
FeatureFlag.MINECART_IMPROVEMENTS, FeatureFlags.MINECART_IMPROVEMENTS,
FeatureFlag.REDSTONE_EXPERIMENTS, FeatureFlags.REDSTONE_EXPERIMENTS
);
@Override
public Set<FeatureFlag> requiredFeatures(final FeatureDependant dependant) {
final FeatureFlagSet requiredFeatures = getFeatureElement(dependant).requiredFeatures();
return fromNms(requiredFeatures);
}
public static Set<FeatureFlag> fromNms(final FeatureFlagSet flagSet) {
final Set<FeatureFlag> flags = new HashSet<>();
for (final net.minecraft.world.flag.FeatureFlag nmsFlag : FeatureFlags.REGISTRY.names.values()) {
if (flagSet.contains(nmsFlag)) {
flags.add(FLAGS.inverse().get(nmsFlag));
}
}
return Collections.unmodifiableSet(flags);
}
static FeatureElement getFeatureElement(final FeatureDependant dependant) {
if (dependant instanceof final EntityType entityType) {
// TODO remove when EntityType is server-backed
return CraftEntityType.bukkitToMinecraft(entityType);
} else if (dependant instanceof final PotionType potionType) {
return CraftPotionType.bukkitToMinecraft(potionType);
} else {
throw new IllegalArgumentException(dependant + " is not a valid feature dependant");
}
}
}

View file

@ -1,51 +0,0 @@
package org.bukkit.craftbukkit;
import java.util.HashSet;
import java.util.Set;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.flag.FeatureFlagSet;
import net.minecraft.world.flag.FeatureFlags;
import org.bukkit.FeatureFlag;
import org.bukkit.NamespacedKey;
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
import org.jetbrains.annotations.NotNull;
public class CraftFeatureFlag implements FeatureFlag {
private final NamespacedKey namespacedKey;
private final net.minecraft.world.flag.FeatureFlag featureFlag;
public CraftFeatureFlag(ResourceLocation minecraftKey, net.minecraft.world.flag.FeatureFlag featureFlag) {
this.namespacedKey = CraftNamespacedKey.fromMinecraft(minecraftKey);
this.featureFlag = featureFlag;
}
public net.minecraft.world.flag.FeatureFlag getHandle() {
return this.featureFlag;
}
@NotNull
@Override
public NamespacedKey getKey() {
return this.namespacedKey;
}
@Override
public String toString() {
return "CraftFeatureFlag{key=" + this.getKey() + ",keyUniverse=" + this.getHandle().universe.toString() + "}";
}
public static Set<CraftFeatureFlag> getFromNMS(FeatureFlagSet featureFlagSet) {
Set<CraftFeatureFlag> set = new HashSet<>();
FeatureFlags.REGISTRY.names.forEach((minecraftkey, featureflag) -> {
if (featureFlagSet.contains(featureflag)) {
set.add(new CraftFeatureFlag(minecraftkey, featureflag));
}
});
return set;
}
public static CraftFeatureFlag getFromNMS(NamespacedKey namespacedKey) {
return FeatureFlags.REGISTRY.names.entrySet().stream().filter(entry -> CraftNamespacedKey.fromMinecraft(entry.getKey()).equals(namespacedKey)).findFirst().map(entry -> new CraftFeatureFlag(entry.getKey(), entry.getValue())).orElse(null);
}
}

View file

@ -564,4 +564,11 @@ public abstract class CraftRegionAccessor implements RegionAccessor {
return !this.getHandle().noCollision(aabb);
}
// Paper end
// Paper start - feature flag API
@Override
public java.util.Set<org.bukkit.FeatureFlag> getFeatureFlags() {
return io.papermc.paper.world.flag.PaperFeatureFlagProviderImpl.fromNms(this.getHandle().enabledFeatures());
}
// Paper end - feature flag API
}

View file

@ -2359,10 +2359,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
return this.persistentDataContainer;
}
@Override
public Set<FeatureFlag> getFeatureFlags() {
return CraftFeatureFlag.getFromNMS(this.getHandle().enabledFeatures()).stream().map(FeatureFlag.class::cast).collect(Collectors.toUnmodifiableSet());
}
// Paper - replace feature flag API
public void storeBukkitValues(CompoundTag c) {
if (!this.persistentDataContainer.isEmpty()) {

View file

@ -33,7 +33,7 @@ import org.bukkit.craftbukkit.util.Handleable;
import org.bukkit.inventory.ItemType;
import org.jetbrains.annotations.NotNull;
public class CraftBlockType<B extends BlockData> implements BlockType.Typed<B>, Handleable<Block> {
public class CraftBlockType<B extends BlockData> implements BlockType.Typed<B>, Handleable<Block>, io.papermc.paper.world.flag.PaperFeatureDependent { // Paper - feature flag API
private final NamespacedKey key;
private final Block block;

View file

@ -17,6 +17,7 @@ public class CraftWorldInfo implements WorldInfo {
private final long seed;
private final int minHeight;
private final int maxHeight;
private final net.minecraft.world.flag.FeatureFlagSet enabledFeatures; // Paper - feature flag API
// Paper start
private final net.minecraft.world.level.chunk.ChunkGenerator vanillaChunkGenerator;
private final net.minecraft.core.RegistryAccess.Frozen registryAccess;
@ -31,6 +32,7 @@ public class CraftWorldInfo implements WorldInfo {
this.seed = ((PrimaryLevelData) worldDataServer).worldGenOptions().seed();
this.minHeight = dimensionManager.minY();
this.maxHeight = dimensionManager.minY() + dimensionManager.height();
this.enabledFeatures = worldDataServer.enabledFeatures(); // Paper - feature flag API
}
@Override
@ -92,4 +94,11 @@ public class CraftWorldInfo implements WorldInfo {
};
}
// Paper end
// Paper start - feature flag API
@Override
public java.util.Set<org.bukkit.FeatureFlag> getFeatureFlags() {
return io.papermc.paper.world.flag.PaperFeatureFlagProviderImpl.fromNms(this.enabledFeatures);
}
// Paper end - feature flag API
}

View file

@ -36,7 +36,7 @@ import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class CraftItemType<M extends ItemMeta> implements ItemType.Typed<M>, Handleable<Item> {
public class CraftItemType<M extends ItemMeta> implements ItemType.Typed<M>, Handleable<Item>, io.papermc.paper.world.flag.PaperFeatureDependent { // Paper - feature flag API
private final NamespacedKey key;
private final Item item;

View file

@ -18,7 +18,7 @@ import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.InventoryView;
import org.bukkit.inventory.MenuType;
public class CraftMenuType<V extends InventoryView> implements MenuType.Typed<V>, Handleable<net.minecraft.world.inventory.MenuType<?>> {
public class CraftMenuType<V extends InventoryView> implements MenuType.Typed<V>, Handleable<net.minecraft.world.inventory.MenuType<?>>, io.papermc.paper.world.flag.PaperFeatureDependent { // Paper - make FeatureDependant
private final NamespacedKey key;
private final net.minecraft.world.inventory.MenuType<?> handle;

View file

@ -11,7 +11,7 @@ import net.minecraft.util.InclusiveRange;
import org.bukkit.Bukkit;
import org.bukkit.FeatureFlag;
import org.bukkit.NamespacedKey;
import org.bukkit.craftbukkit.CraftFeatureFlag;
// import org.bukkit.craftbukkit.CraftFeatureFlag; // Paper - replace feature flag API
import org.bukkit.craftbukkit.CraftServer;
import org.bukkit.craftbukkit.util.CraftChatMessage;
import org.bukkit.packs.DataPack;
@ -98,7 +98,7 @@ public class CraftDataPack implements DataPack {
@Override
public Set<FeatureFlag> getRequestedFeatures() {
return CraftFeatureFlag.getFromNMS(this.getHandle().getRequestedFeatures()).stream().map(FeatureFlag.class::cast).collect(Collectors.toUnmodifiableSet());
return io.papermc.paper.world.flag.PaperFeatureFlagProviderImpl.fromNms(this.getHandle().getRequestedFeatures()); // Paper - replace feature flag API
}
@Override

View file

@ -13,7 +13,7 @@ import org.bukkit.potion.PotionEffectType;
import org.bukkit.potion.PotionEffectTypeCategory;
import org.jetbrains.annotations.NotNull;
public class CraftPotionEffectType extends PotionEffectType implements Handleable<MobEffect> {
public class CraftPotionEffectType extends PotionEffectType implements Handleable<MobEffect>, io.papermc.paper.world.flag.PaperFeatureDependent { // Paper - feature flag API
public static PotionEffectType minecraftHolderToBukkit(Holder<MobEffect> minecraft) {
return CraftPotionEffectType.minecraftToBukkit(minecraft.value());

View file

@ -48,7 +48,7 @@ import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeModifier;
import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData;
import org.bukkit.craftbukkit.CraftFeatureFlag;
// import org.bukkit.craftbukkit.CraftFeatureFlag; // Paper
import org.bukkit.craftbukkit.CraftRegistry;
import org.bukkit.craftbukkit.CraftServer;
import org.bukkit.craftbukkit.block.CraftBiome;
@ -456,11 +456,7 @@ public final class CraftMagicNumbers implements UnsafeValues {
return attribute.getTranslationKey();
}
@Override
public FeatureFlag getFeatureFlag(NamespacedKey namespacedKey) {
Preconditions.checkArgument(namespacedKey != null, "NamespaceKey cannot be null");
return CraftFeatureFlag.getFromNMS(namespacedKey);
}
// Paper - replace feature flag API
@Override
public PotionType.InternalPotionData getInternalPotionData(NamespacedKey namespacedKey) {

View file

@ -0,0 +1 @@
io.papermc.paper.world.flag.PaperFeatureFlagProviderImpl

View file

@ -0,0 +1 @@
io.papermc.paper.world.flag.PaperFeatureFlagProviderImpl

View file

@ -0,0 +1,99 @@
package io.papermc.paper.world.flag;
import io.papermc.paper.adventure.PaperAdventure;
import io.papermc.paper.registry.PaperRegistries;
import io.papermc.paper.registry.RegistryAccess;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.entry.RegistryEntry;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Stream;
import net.kyori.adventure.key.Key;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.flag.FeatureElement;
import net.minecraft.world.flag.FeatureFlagSet;
import net.minecraft.world.flag.FeatureFlags;
import org.bukkit.FeatureFlag;
import org.bukkit.Keyed;
import org.bukkit.support.RegistryHelper;
import org.bukkit.support.environment.AllFeatures;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
@AllFeatures
class FeatureFlagTest {
@Test
void testFeatureFlagParity() {
final Set<ResourceLocation> locations = new HashSet<>();
for (final FeatureFlag flag : FeatureFlag.ALL_FLAGS.values()) {
locations.add(PaperAdventure.asVanilla(flag.getKey()));
}
FeatureFlags.REGISTRY.fromNames(locations, unknown -> {
fail("Unknown api feature flag: " + unknown);
});
for (final ResourceLocation nmsFlag : allNames()) {
assertNotNull(FeatureFlag.ALL_FLAGS.value(Key.key(nmsFlag.toString())), "can't find api flag for " + nmsFlag);
}
}
@Test
void testFeatureFlagConversion() {
assertEquals(allNames().size(), PaperFeatureFlagProviderImpl.FLAGS.size());
for (final FeatureFlag featureFlag : PaperFeatureFlagProviderImpl.FLAGS.keySet()) {
final net.minecraft.world.flag.FeatureFlag nmsFlag = PaperFeatureFlagProviderImpl.FLAGS.get(featureFlag);
final ResourceLocation nmsFlagName = FeatureFlags.REGISTRY.toNames(FeatureFlagSet.of(nmsFlag)).iterator().next();
assertEquals(nmsFlagName.toString(), featureFlag.key().asString());
}
}
static Set<ResourceLocation> allNames() {
return FeatureFlags.REGISTRY.toNames(FeatureFlags.REGISTRY.allFlags());
}
@SuppressWarnings({"rawtypes", "unchecked"})
static Set<RegistryKey<?>> featureFilteredRegistries() {
final Set<RegistryKey<?>> registryKeys = new HashSet<>();
for (final ResourceKey filteredRegistry : FeatureElement.FILTERED_REGISTRIES) {
registryKeys.add(PaperRegistries.registryFromNms(filteredRegistry));
}
return registryKeys;
}
@MethodSource("featureFilteredRegistries")
@ParameterizedTest
<T extends Keyed> void testApiImplementsFeatureDependant(final RegistryKey<T> registryKey) {
final org.bukkit.Registry<T> registry = RegistryAccess.registryAccess().getRegistry(registryKey);
final T anyElement = registry.iterator().next();
assertInstanceOf(FeatureDependant.class, anyElement, "Registry " + registryKey + " doesn't have feature dependent elements");
final FeatureDependant dependant = ((FeatureDependant) anyElement);
assertDoesNotThrow(dependant::requiredFeatures, "Failed to get required features for " + anyElement + " in " + registryKey);
}
static Stream<RegistryKey<?>> nonFeatureFilteredRegistries() {
return RegistryHelper.getRegistry().registries().filter(r -> {
final RegistryEntry<?, ?> entry = PaperRegistries.getEntry(r.key());
// has an API registry and isn't a filtered registry
return entry != null && !FeatureElement.FILTERED_REGISTRIES.contains(r.key());
}).map(r -> PaperRegistries.getEntry(r.key()).apiKey());
}
@MethodSource("nonFeatureFilteredRegistries")
@ParameterizedTest
<T extends Keyed> void testApiDoesntImplementFeatureDependant(final RegistryKey<T> registryKey) {
final org.bukkit.Registry<T> registry = RegistryAccess.registryAccess().getRegistry(registryKey);
final T anyElement = registry.iterator().next();
assertFalse(anyElement instanceof FeatureDependant, "Registry " + registryKey + " has feature dependent elements");
}
}