package org.bukkit.craftbukkit; import com.google.common.base.Preconditions; import io.papermc.paper.registry.PaperRegistries; import io.papermc.paper.registry.RegistryAccess; import io.papermc.paper.registry.RegistryKey; import io.papermc.paper.registry.entry.RegistryEntryMeta; import io.papermc.paper.registry.set.NamedRegistryKeySetImpl; import io.papermc.paper.registry.tag.Tag; import io.papermc.paper.util.Holderable; import io.papermc.paper.util.MCUtil; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Optional; import java.util.function.BiFunction; import java.util.stream.Stream; import net.minecraft.core.Holder; import net.minecraft.core.HolderOwner; import net.minecraft.resources.ResourceKey; import org.bukkit.Keyed; import org.bukkit.NamespacedKey; import org.bukkit.Particle; import org.bukkit.Registry; import org.bukkit.craftbukkit.legacy.FieldRename; import org.bukkit.craftbukkit.util.ApiVersion; import org.bukkit.craftbukkit.util.CraftNamespacedKey; import org.bukkit.craftbukkit.util.Handleable; import org.bukkit.entity.EntityType; import org.jetbrains.annotations.NotNull; public class CraftRegistry implements Registry { private static net.minecraft.core.RegistryAccess registry; public static void setMinecraftRegistry(final net.minecraft.core.RegistryAccess registry) { Preconditions.checkState(CraftRegistry.registry == null, "Registry already set"); CraftRegistry.registry = registry; } public static net.minecraft.core.RegistryAccess getMinecraftRegistry() { return CraftRegistry.registry; } public static net.minecraft.core.Registry getMinecraftRegistry(ResourceKey> key) { return CraftRegistry.getMinecraftRegistry().lookupOrThrow(key); } /** * Usage note: Only use this method to delegate the conversion methods from the individual Craft classes to here. * Do not use it in other parts of CraftBukkit, use the methods in the respective Craft classes instead. * * @param minecraft the minecraft representation * @param registryKey the registry key of the minecraft registry to use * @return the bukkit representation of the minecraft value */ public static B minecraftToBukkit(M minecraft, ResourceKey> registryKey) { Preconditions.checkArgument(minecraft != null); net.minecraft.core.Registry registry = CraftRegistry.getMinecraftRegistry(registryKey); final Registry bukkitRegistry = RegistryAccess.registryAccess().getRegistry(PaperRegistries.registryFromNms(registryKey)); final java.util.Optional> resourceKey = registry.getResourceKey(minecraft); if (resourceKey.isEmpty() && bukkitRegistry instanceof final CraftRegistry craftRegistry && craftRegistry.supportsDirectHolders()) { return ((CraftRegistry) bukkitRegistry).createBukkit(Holder.direct(minecraft)); } else if (resourceKey.isEmpty()) { throw new IllegalStateException(String.format("Cannot convert '%s' to bukkit representation, since it is not registered.", minecraft)); } final B bukkit = bukkitRegistry.get(CraftNamespacedKey.fromMinecraft(resourceKey.get().location())); Preconditions.checkArgument(bukkit != null); return bukkit; } public static B minecraftHolderToBukkit(final Holder minecraft, final ResourceKey> registryKey) { Preconditions.checkArgument(minecraft != null); final Registry bukkitRegistry = RegistryAccess.registryAccess().getRegistry(PaperRegistries.registryFromNms(registryKey)); final B bukkit = switch (minecraft) { case final Holder.Direct direct -> { if (!(bukkitRegistry instanceof final CraftRegistry craftRegistry) || !craftRegistry.supportsDirectHolders()) { throw new IllegalArgumentException("Cannot convert direct holder to bukkit representation"); } yield ((CraftRegistry) bukkitRegistry).createBukkit(direct); } case final Holder.Reference reference -> bukkitRegistry.get(MCUtil.fromResourceKey(reference.key())); default -> throw new IllegalArgumentException("Unknown holder: " + minecraft); }; Preconditions.checkArgument(bukkit != null); return bukkit; } /** * Usage note: Only use this method to delegate the conversion methods from the individual Craft classes to here. * Do not use it in other parts of CraftBukkit, use the methods in the respective Craft classes instead. * * @param bukkit the bukkit representation * @return the minecraft representation of the bukkit value */ public static M bukkitToMinecraft(B bukkit) { Preconditions.checkArgument(bukkit != null); return ((Handleable) bukkit).getHandle(); } public static Holder bukkitToMinecraftHolder(B bukkit, ResourceKey> registryKey) { Preconditions.checkArgument(bukkit != null); // Paper start - support direct Holder if (bukkit instanceof io.papermc.paper.util.Holderable) { return ((io.papermc.paper.util.Holderable) bukkit).getHolder(); } // Paper end - support direct Holder net.minecraft.core.Registry registry = CraftRegistry.getMinecraftRegistry(registryKey); if (registry.wrapAsHolder(CraftRegistry.bukkitToMinecraft(bukkit)) instanceof Holder.Reference holder) { return holder; } throw new IllegalArgumentException("No Reference holder found for " + bukkit + ", this can happen if a plugin creates its own registry entry with out properly registering it."); } // Paper start - fixup upstream being dum public static Optional unwrapAndConvertHolder(final RegistryKey registryKey, final Holder value) { final Registry registry = RegistryAccess.registryAccess().getRegistry(registryKey); if (registry instanceof final CraftRegistry craftRegistry && craftRegistry.supportsDirectHolders() && value.kind() == Holder.Kind.DIRECT) { return Optional.of(((CraftRegistry) registry).createBukkit(value)); } return value.unwrapKey().map(key -> registry.get(CraftNamespacedKey.fromMinecraft(key.location()))); } // Paper end - fixup upstream being dum // Paper - move to PaperRegistries // Paper - NOTE: As long as all uses of the method below relate to *serialization* via ConfigurationSerializable, it's fine public static B get(RegistryKey bukkitKey, NamespacedKey namespacedKey, ApiVersion apiVersion) { final Registry bukkit = RegistryAccess.registryAccess().getRegistry(bukkitKey); if (bukkit instanceof CraftRegistry craft) { return craft.get(craft.serializationUpdater.apply(namespacedKey, apiVersion)); // Paper } if (bukkit instanceof Registry.SimpleRegistry simple) { Class bClass = simple.getType(); if (bClass == EntityType.class) { return bukkit.get(FieldRename.ENTITY_TYPE_RENAME.apply(namespacedKey, apiVersion)); } if (bClass == Particle.class) { return bukkit.get(FieldRename.PARTICLE_TYPE_RENAME.apply(namespacedKey, apiVersion)); } } return bukkit.get(namespacedKey); } private final Class bukkitClass; // Paper - relax preload class private final Map cache = new HashMap<>(); private final net.minecraft.core.Registry minecraftRegistry; private final io.papermc.paper.registry.entry.RegistryTypeMapper minecraftToBukkit; // Paper - switch to Holder private final BiFunction serializationUpdater; // Paper - rename to make it *clear* what it is *only* for private final InvalidHolderOwner invalidHolderOwner = new InvalidHolderOwner(); private boolean lockReferenceHolders; public CraftRegistry(Class bukkitClass, net.minecraft.core.Registry minecraftRegistry, BiFunction minecraftToBukkit, BiFunction serializationUpdater) { // Paper - relax preload class // Paper start - switch to Holder this(bukkitClass, minecraftRegistry, new io.papermc.paper.registry.entry.RegistryTypeMapper<>(minecraftToBukkit), serializationUpdater); } public CraftRegistry(final RegistryEntryMeta.ServerSide meta, final net.minecraft.core.Registry minecraftRegistry) { this(meta.classToPreload(), minecraftRegistry, meta.registryTypeMapper(), meta.serializationUpdater()); } public CraftRegistry(Class bukkitClass, net.minecraft.core.Registry minecraftRegistry, io.papermc.paper.registry.entry.RegistryTypeMapper minecraftToBukkit, BiFunction serializationUpdater) { // Paper - relax preload class // Paper end - support Holders this.bukkitClass = bukkitClass; this.minecraftRegistry = minecraftRegistry; this.minecraftToBukkit = minecraftToBukkit; this.serializationUpdater = serializationUpdater; this.lockReferenceHolders = !this.minecraftToBukkit.constructorUsesHolder(); } public void lockReferenceHolders() { Preconditions.checkState(this.cache.isEmpty(), "Registry %s is already loaded", this.minecraftRegistry.key()); try { Class.forName(this.bukkitClass.getName()); // this should always trigger the initialization of the class } catch (final ClassNotFoundException e) { throw new IllegalStateException("Failed to load class " + this.bukkitClass.getSimpleName(), e); } if (!this.minecraftToBukkit.constructorUsesHolder()) { return; } Preconditions.checkState(!this.lockReferenceHolders, "Reference holders are already locked"); this.lockReferenceHolders = true; } // Paper - inline into CraftRegistry#get(Registry, NamespacedKey, ApiVersion) above @Override public B get(NamespacedKey namespacedKey) { B cached = this.cache.get(namespacedKey); if (cached != null) { return cached; } final Optional> holderOptional = this.minecraftRegistry.get(CraftNamespacedKey.toMinecraft(namespacedKey)); final Holder.Reference holder; if (holderOptional.isPresent()) { holder = holderOptional.get(); } else if (!this.lockReferenceHolders && this.minecraftToBukkit.constructorUsesHolder()) { // only works if its Holderable // we lock the reference holders after the preload class has been initialized // this is to support the vanilla mechanic of preventing vanilla registry entries being loaded. We need // to create something to fill the API constant fields, so we create a dummy reference holder. holder = Holder.Reference.createStandAlone(this.invalidHolderOwner, MCUtil.toResourceKey(this.minecraftRegistry.key(), namespacedKey)); } else { holder = null; } final B bukkit = this.createBukkit(holder); if (bukkit == null) { return null; } this.cache.put(namespacedKey, bukkit); return bukkit; } @NotNull @Override public Stream stream() { return this.minecraftRegistry.keySet().stream().map(minecraftKey -> this.get(CraftNamespacedKey.fromMinecraft(minecraftKey))); } @Override public Iterator iterator() { return this.stream().iterator(); } public B createBukkit(Holder minecraft) { if (minecraft == null) { return null; } return this.minecraftToBukkit.createBukkit(minecraft); } public boolean supportsDirectHolders() { return this.minecraftToBukkit.supportsDirectHolders(); } // Paper start - improve Registry @Override public NamespacedKey getKey(final B value) { if (value instanceof Holderable holderable) { return holderable.getKeyOrNull(); } return value.getKey(); } // Paper end - improve Registry // Paper start - RegistrySet API @Override public boolean hasTag(final io.papermc.paper.registry.tag.TagKey key) { return this.minecraftRegistry.get(net.minecraft.tags.TagKey.create(this.minecraftRegistry.key(), io.papermc.paper.adventure.PaperAdventure.asVanilla(key.key()))).isPresent(); } @Override public io.papermc.paper.registry.tag.Tag getTag(final io.papermc.paper.registry.tag.TagKey key) { final net.minecraft.core.HolderSet.Named namedHolderSet = this.minecraftRegistry.get(io.papermc.paper.registry.PaperRegistries.toNms(key)).orElseThrow(); return new io.papermc.paper.registry.set.NamedRegistryKeySetImpl<>(key, namedHolderSet); } @Override public Collection> getTags() { return this.minecraftRegistry.getTags().>map(NamedRegistryKeySetImpl::new).toList(); } // Paper end - RegistrySet API final class InvalidHolderOwner implements HolderOwner { } }