package net.minecraft.server; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; import com.mojang.logging.LogUtils; import com.mojang.serialization.JsonOps; import com.mojang.serialization.Lifecycle; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.stream.Stream; import net.minecraft.Util; import net.minecraft.core.HolderGetter; import net.minecraft.core.HolderLookup; import net.minecraft.core.LayeredRegistryAccess; import net.minecraft.core.MappedRegistry; import net.minecraft.core.RegistrationInfo; import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; import net.minecraft.core.WritableRegistry; import net.minecraft.core.registries.Registries; import net.minecraft.resources.RegistryOps; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener; import net.minecraft.util.ProblemReporter; import net.minecraft.world.level.storage.loot.BuiltInLootTables; import net.minecraft.world.level.storage.loot.LootDataType; import net.minecraft.world.level.storage.loot.LootTable; import net.minecraft.world.level.storage.loot.ValidationContext; import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets; import org.slf4j.Logger; public class ReloadableServerRegistries { private static final Logger LOGGER = LogUtils.getLogger(); private static final Gson GSON = new GsonBuilder().create(); private static final RegistrationInfo DEFAULT_REGISTRATION_INFO = new RegistrationInfo(Optional.empty(), Lifecycle.experimental()); public static CompletableFuture> reload( LayeredRegistryAccess registries, ResourceManager resourceManager, Executor backgroundExecutor ) { RegistryAccess.Frozen frozen = registries.getAccessForLoading(RegistryLayer.RELOADABLE); RegistryOps registryOps = new ReloadableServerRegistries.EmptyTagLookupWrapper(frozen).createSerializationContext(JsonOps.INSTANCE); List>> list = LootDataType.values() .map(lootDataType -> scheduleElementParse(lootDataType, registryOps, resourceManager, backgroundExecutor)) .toList(); CompletableFuture>> completableFuture = Util.sequence(list); return completableFuture.thenApplyAsync(listx -> apply(registries, listx), backgroundExecutor); } private static CompletableFuture> scheduleElementParse( LootDataType lootDataType, RegistryOps registryOps, ResourceManager resourceManager, Executor backgroundExecutor ) { return CompletableFuture.supplyAsync( () -> { WritableRegistry writableRegistry = new MappedRegistry<>(lootDataType.registryKey(), Lifecycle.experimental()); Map map = new HashMap(); String string = Registries.elementsDirPath(lootDataType.registryKey()); SimpleJsonResourceReloadListener.scanDirectory(resourceManager, string, GSON, map); map.forEach( (resourceLocation, jsonElement) -> lootDataType.deserialize(resourceLocation, registryOps, jsonElement) .ifPresent(object -> writableRegistry.register(ResourceKey.create(lootDataType.registryKey(), resourceLocation), (T)object, DEFAULT_REGISTRATION_INFO)) ); return writableRegistry; }, backgroundExecutor ); } private static LayeredRegistryAccess apply(LayeredRegistryAccess registryAccess, List> registries) { LayeredRegistryAccess layeredRegistryAccess = createUpdatedRegistries(registryAccess, registries); ProblemReporter.Collector collector = new ProblemReporter.Collector(); RegistryAccess.Frozen frozen = layeredRegistryAccess.compositeAccess(); ValidationContext validationContext = new ValidationContext(collector, LootContextParamSets.ALL_PARAMS, frozen.asGetterLookup()); LootDataType.values().forEach(lootDataType -> validateRegistry(validationContext, lootDataType, frozen)); collector.get().forEach((string, string2) -> LOGGER.warn("Found loot table element validation problem in {}: {}", string, string2)); return layeredRegistryAccess; } private static LayeredRegistryAccess createUpdatedRegistries( LayeredRegistryAccess registryAccess, List> registries ) { RegistryAccess registryAccess2 = new RegistryAccess.ImmutableRegistryAccess(registries); ((WritableRegistry)registryAccess2.registryOrThrow(Registries.LOOT_TABLE)) .register(BuiltInLootTables.EMPTY, LootTable.EMPTY, DEFAULT_REGISTRATION_INFO); return registryAccess.replaceFrom(RegistryLayer.RELOADABLE, registryAccess2.freeze()); } private static void validateRegistry(ValidationContext context, LootDataType lootDataType, RegistryAccess registryAccess) { Registry registry = registryAccess.registryOrThrow(lootDataType.registryKey()); registry.holders().forEach(reference -> lootDataType.runValidation(context, reference.key(), (T)reference.value())); } static class EmptyTagLookupWrapper implements HolderLookup.Provider { private final RegistryAccess registryAccess; EmptyTagLookupWrapper(RegistryAccess registryAccess) { this.registryAccess = registryAccess; } @Override public Stream>> listRegistries() { return this.registryAccess.listRegistries(); } @Override public Optional> lookup(ResourceKey> registryKey) { return this.registryAccess.registry(registryKey).map(Registry::asTagAddingLookup); } } public static class Holder { private final RegistryAccess.Frozen registries; public Holder(RegistryAccess.Frozen registries) { this.registries = registries; } public RegistryAccess.Frozen get() { return this.registries; } public HolderGetter.Provider lookup() { return this.registries.asGetterLookup(); } public Collection getKeys(ResourceKey> registryKey) { return this.registries.registry(registryKey).stream().flatMap(registry -> registry.holders().map(reference -> reference.key().location())).toList(); } public LootTable getLootTable(ResourceKey lootTableKey) { return (LootTable)this.registries .lookup(Registries.LOOT_TABLE) .flatMap(registryLookup -> registryLookup.get(lootTableKey)) .map(net.minecraft.core.Holder::value) .orElse(LootTable.EMPTY); } } }