package net.minecraft.resources; import com.google.gson.JsonElement; import com.google.gson.JsonParser; import com.mojang.logging.LogUtils; import com.mojang.serialization.Codec; import com.mojang.serialization.DataResult; import com.mojang.serialization.Decoder; import com.mojang.serialization.JsonOps; import com.mojang.serialization.Lifecycle; import java.io.IOException; import java.io.PrintWriter; import java.io.Reader; import java.io.StringWriter; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.Map.Entry; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.stream.Collectors; import net.minecraft.CrashReport; import net.minecraft.CrashReportCategory; import net.minecraft.CrashReportDetail; import net.minecraft.ReportedException; import net.minecraft.Util; import net.minecraft.core.HolderLookup; import net.minecraft.core.MappedRegistry; import net.minecraft.core.RegistrationInfo; import net.minecraft.core.Registry; import net.minecraft.core.WritableRegistry; import net.minecraft.core.RegistryAccess.Frozen; import net.minecraft.core.RegistryAccess.ImmutableRegistryAccess; import net.minecraft.core.RegistrySynchronization.PackedRegistryEntry; import net.minecraft.core.registries.Registries; import net.minecraft.gametest.framework.GameTestInstance; import net.minecraft.gametest.framework.TestEnvironmentDefinition; import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.Tag; import net.minecraft.network.chat.ChatType; import net.minecraft.server.packs.repository.KnownPack; import net.minecraft.server.packs.resources.Resource; import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.server.packs.resources.ResourceProvider; import net.minecraft.tags.TagLoader; import net.minecraft.tags.TagNetworkSerialization.NetworkPayload; import net.minecraft.world.damagesource.DamageType; import net.minecraft.world.entity.animal.CatVariant; import net.minecraft.world.entity.animal.ChickenVariant; import net.minecraft.world.entity.animal.CowVariant; import net.minecraft.world.entity.animal.PigVariant; import net.minecraft.world.entity.animal.frog.FrogVariant; import net.minecraft.world.entity.animal.wolf.WolfSoundVariant; import net.minecraft.world.entity.animal.wolf.WolfVariant; import net.minecraft.world.entity.decoration.PaintingVariant; import net.minecraft.world.item.Instrument; import net.minecraft.world.item.JukeboxSong; import net.minecraft.world.item.enchantment.Enchantment; import net.minecraft.world.item.enchantment.providers.EnchantmentProvider; import net.minecraft.world.item.equipment.trim.TrimMaterial; import net.minecraft.world.item.equipment.trim.TrimPattern; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.MultiNoiseBiomeSourceParameterList; import net.minecraft.world.level.block.entity.BannerPattern; import net.minecraft.world.level.block.entity.trialspawner.TrialSpawnerConfig; import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.level.dimension.LevelStem; import net.minecraft.world.level.levelgen.DensityFunction; import net.minecraft.world.level.levelgen.NoiseGeneratorSettings; import net.minecraft.world.level.levelgen.carver.ConfiguredWorldCarver; import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorPreset; import net.minecraft.world.level.levelgen.placement.PlacedFeature; import net.minecraft.world.level.levelgen.presets.WorldPreset; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureSet; import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool; import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorType; import net.minecraft.world.level.levelgen.synth.NormalNoise.NoiseParameters; import org.slf4j.Logger; public class RegistryDataLoader { private static final Logger LOGGER = LogUtils.getLogger(); private static final Comparator> ERROR_KEY_COMPARATOR = Comparator.comparing(ResourceKey::registry).thenComparing(ResourceKey::location); private static final RegistrationInfo NETWORK_REGISTRATION_INFO = new RegistrationInfo(Optional.empty(), Lifecycle.experimental()); private static final Function, RegistrationInfo> REGISTRATION_INFO_CACHE = Util.memoize( (Function, RegistrationInfo>)(optional -> { Lifecycle lifecycle = (Lifecycle)optional.map(KnownPack::isVanilla).map(boolean_ -> Lifecycle.stable()).orElse(Lifecycle.experimental()); return new RegistrationInfo(optional, lifecycle); }) ); public static final List> WORLDGEN_REGISTRIES = List.of( new RegistryDataLoader.RegistryData<>(Registries.DIMENSION_TYPE, DimensionType.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.BIOME, Biome.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.CHAT_TYPE, ChatType.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.CONFIGURED_CARVER, ConfiguredWorldCarver.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.CONFIGURED_FEATURE, ConfiguredFeature.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.PLACED_FEATURE, PlacedFeature.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.STRUCTURE, Structure.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.STRUCTURE_SET, StructureSet.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.PROCESSOR_LIST, StructureProcessorType.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.TEMPLATE_POOL, StructureTemplatePool.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.NOISE_SETTINGS, NoiseGeneratorSettings.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.NOISE, NoiseParameters.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.DENSITY_FUNCTION, DensityFunction.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.WORLD_PRESET, WorldPreset.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.FLAT_LEVEL_GENERATOR_PRESET, FlatLevelGeneratorPreset.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.TRIM_PATTERN, TrimPattern.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.TRIM_MATERIAL, TrimMaterial.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.TRIAL_SPAWNER_CONFIG, TrialSpawnerConfig.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.WOLF_VARIANT, WolfVariant.DIRECT_CODEC, true), new RegistryDataLoader.RegistryData<>(Registries.WOLF_SOUND_VARIANT, WolfSoundVariant.DIRECT_CODEC, true), new RegistryDataLoader.RegistryData<>(Registries.PIG_VARIANT, PigVariant.DIRECT_CODEC, true), new RegistryDataLoader.RegistryData<>(Registries.FROG_VARIANT, FrogVariant.DIRECT_CODEC, true), new RegistryDataLoader.RegistryData<>(Registries.CAT_VARIANT, CatVariant.DIRECT_CODEC, true), new RegistryDataLoader.RegistryData<>(Registries.COW_VARIANT, CowVariant.DIRECT_CODEC, true), new RegistryDataLoader.RegistryData<>(Registries.CHICKEN_VARIANT, ChickenVariant.DIRECT_CODEC, true), new RegistryDataLoader.RegistryData<>(Registries.PAINTING_VARIANT, PaintingVariant.DIRECT_CODEC, true), new RegistryDataLoader.RegistryData<>(Registries.DAMAGE_TYPE, DamageType.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.MULTI_NOISE_BIOME_SOURCE_PARAMETER_LIST, MultiNoiseBiomeSourceParameterList.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.BANNER_PATTERN, BannerPattern.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.ENCHANTMENT, Enchantment.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.ENCHANTMENT_PROVIDER, EnchantmentProvider.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.JUKEBOX_SONG, JukeboxSong.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.INSTRUMENT, Instrument.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.TEST_ENVIRONMENT, TestEnvironmentDefinition.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.TEST_INSTANCE, GameTestInstance.DIRECT_CODEC) ); public static final List> DIMENSION_REGISTRIES = List.of( new RegistryDataLoader.RegistryData<>(Registries.LEVEL_STEM, LevelStem.CODEC) ); public static final List> SYNCHRONIZED_REGISTRIES = List.of( new RegistryDataLoader.RegistryData<>(Registries.BIOME, Biome.NETWORK_CODEC), new RegistryDataLoader.RegistryData<>(Registries.CHAT_TYPE, ChatType.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.TRIM_PATTERN, TrimPattern.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.TRIM_MATERIAL, TrimMaterial.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.WOLF_VARIANT, WolfVariant.NETWORK_CODEC, true), new RegistryDataLoader.RegistryData<>(Registries.WOLF_SOUND_VARIANT, WolfSoundVariant.NETWORK_CODEC, true), new RegistryDataLoader.RegistryData<>(Registries.PIG_VARIANT, PigVariant.NETWORK_CODEC, true), new RegistryDataLoader.RegistryData<>(Registries.FROG_VARIANT, FrogVariant.NETWORK_CODEC, true), new RegistryDataLoader.RegistryData<>(Registries.CAT_VARIANT, CatVariant.NETWORK_CODEC, true), new RegistryDataLoader.RegistryData<>(Registries.COW_VARIANT, CowVariant.NETWORK_CODEC, true), new RegistryDataLoader.RegistryData<>(Registries.CHICKEN_VARIANT, ChickenVariant.NETWORK_CODEC, true), new RegistryDataLoader.RegistryData<>(Registries.PAINTING_VARIANT, PaintingVariant.DIRECT_CODEC, true), new RegistryDataLoader.RegistryData<>(Registries.DIMENSION_TYPE, DimensionType.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.DAMAGE_TYPE, DamageType.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.BANNER_PATTERN, BannerPattern.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.ENCHANTMENT, Enchantment.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.JUKEBOX_SONG, JukeboxSong.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.INSTRUMENT, Instrument.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.TEST_ENVIRONMENT, TestEnvironmentDefinition.DIRECT_CODEC), new RegistryDataLoader.RegistryData<>(Registries.TEST_INSTANCE, GameTestInstance.DIRECT_CODEC) ); public static Frozen load( ResourceManager resourceManager, List> registryLookups, List> registryData ) { return load((loader, registryInfoLookup) -> loader.loadFromResources(resourceManager, registryInfoLookup), registryLookups, registryData); } public static Frozen load( Map>, RegistryDataLoader.NetworkedRegistryData> elements, ResourceProvider resourceProvider, List> registryLookups, List> registryData ) { return load((loader, registryInfoLookup) -> loader.loadFromNetwork(elements, resourceProvider, registryInfoLookup), registryLookups, registryData); } private static Frozen load( RegistryDataLoader.LoadingFunction loadingFunction, List> registryLookups, List> registryData ) { Map, Exception> map = new HashMap(); List> list = (List>)registryData.stream() .map(registryDatax -> registryDatax.create(Lifecycle.stable(), map)) .collect(Collectors.toUnmodifiableList()); RegistryOps.RegistryInfoLookup registryInfoLookup = createContext(registryLookups, list); list.forEach(loader -> loadingFunction.apply(loader, registryInfoLookup)); list.forEach(loader -> { Registry registry = loader.registry(); try { registry.freeze(); } catch (Exception var4x) { map.put(registry.key(), var4x); } if (loader.data.requiredNonEmpty && registry.size() == 0) { map.put(registry.key(), new IllegalStateException("Registry must be non-empty: " + registry.key().location())); } }); if (!map.isEmpty()) { throw logErrors(map); } else { return new ImmutableRegistryAccess(list.stream().map(RegistryDataLoader.Loader::registry).toList()).freeze(); } } private static RegistryOps.RegistryInfoLookup createContext(List> registryLookups, List> loaders) { final Map>, RegistryOps.RegistryInfo> map = new HashMap(); registryLookups.forEach(registryLookup -> map.put(registryLookup.key(), createInfoForContextRegistry(registryLookup))); loaders.forEach(loader -> map.put(loader.registry.key(), createInfoForNewRegistry(loader.registry))); return new RegistryOps.RegistryInfoLookup() { @Override public Optional> lookup(ResourceKey> registryKey) { return Optional.ofNullable((RegistryOps.RegistryInfo)map.get(registryKey)); } }; } private static RegistryOps.RegistryInfo createInfoForNewRegistry(WritableRegistry registry) { return new RegistryOps.RegistryInfo<>(registry, registry.createRegistrationLookup(), registry.registryLifecycle()); } private static RegistryOps.RegistryInfo createInfoForContextRegistry(HolderLookup.RegistryLookup registryLookup) { return new RegistryOps.RegistryInfo<>(registryLookup, registryLookup, registryLookup.registryLifecycle()); } private static ReportedException logErrors(Map, Exception> errors) { printFullDetailsToLog(errors); return createReportWithBriefInfo(errors); } private static void printFullDetailsToLog(Map, Exception> errors) { StringWriter stringWriter = new StringWriter(); PrintWriter printWriter = new PrintWriter(stringWriter); Map> map = (Map>)errors.entrySet() .stream() .collect( Collectors.groupingBy( entry -> ((ResourceKey)entry.getKey()).registry(), Collectors.toMap(entry -> ((ResourceKey)entry.getKey()).location(), Entry::getValue) ) ); map.entrySet().stream().sorted(Entry.comparingByKey()).forEach(entry -> { printWriter.printf("> Errors in registry %s:%n", entry.getKey()); ((Map)entry.getValue()).entrySet().stream().sorted(Entry.comparingByKey()).forEach(entryx -> { printWriter.printf(">> Errors in element %s:%n", entryx.getKey()); ((Exception)entryx.getValue()).printStackTrace(printWriter); }); }); printWriter.flush(); LOGGER.error("Registry loading errors:\n{}", stringWriter); } private static ReportedException createReportWithBriefInfo(Map, Exception> errors) { CrashReport crashReport = CrashReport.forThrowable(new IllegalStateException("Failed to load registries due to errors"), "Registry Loading"); CrashReportCategory crashReportCategory = crashReport.addCategory("Loading info"); crashReportCategory.setDetail( "Errors", (CrashReportDetail)(() -> { StringBuilder stringBuilder = new StringBuilder(); errors.entrySet() .stream() .sorted(Entry.comparingByKey(ERROR_KEY_COMPARATOR)) .forEach( entry -> stringBuilder.append("\n\t\t") .append(((ResourceKey)entry.getKey()).registry()) .append("/") .append(((ResourceKey)entry.getKey()).location()) .append(": ") .append(((Exception)entry.getValue()).getMessage()) ); return stringBuilder.toString(); }) ); return new ReportedException(crashReport); } private static void loadElementFromResource( WritableRegistry registry, Decoder codec, RegistryOps ops, ResourceKey resourceKey, Resource resource, RegistrationInfo registrationInfo ) throws IOException { Reader reader = resource.openAsReader(); try { JsonElement jsonElement = JsonParser.parseReader(reader); DataResult dataResult = codec.parse(ops, jsonElement); E object = dataResult.getOrThrow(); registry.register(resourceKey, object, registrationInfo); } catch (Throwable var11) { if (reader != null) { try { reader.close(); } catch (Throwable var10) { var11.addSuppressed(var10); } } throw var11; } if (reader != null) { reader.close(); } } static void loadContentsFromManager( ResourceManager resourceManager, RegistryOps.RegistryInfoLookup registryInfoLookup, WritableRegistry registry, Decoder codec, Map, Exception> loadingErrors ) { FileToIdConverter fileToIdConverter = FileToIdConverter.registry(registry.key()); RegistryOps registryOps = RegistryOps.create(JsonOps.INSTANCE, registryInfoLookup); for (Entry entry : fileToIdConverter.listMatchingResources(resourceManager).entrySet()) { ResourceLocation resourceLocation = (ResourceLocation)entry.getKey(); ResourceKey resourceKey = ResourceKey.create(registry.key(), fileToIdConverter.fileToId(resourceLocation)); Resource resource = (Resource)entry.getValue(); RegistrationInfo registrationInfo = (RegistrationInfo)REGISTRATION_INFO_CACHE.apply(resource.knownPackInfo()); try { loadElementFromResource(registry, codec, registryOps, resourceKey, resource, registrationInfo); } catch (Exception var14) { loadingErrors.put( resourceKey, new IllegalStateException(String.format(Locale.ROOT, "Failed to parse %s from pack %s", resourceLocation, resource.sourcePackId()), var14) ); } } TagLoader.loadTagsForRegistry(resourceManager, registry); } static void loadContentsFromNetwork( Map>, RegistryDataLoader.NetworkedRegistryData> elements, ResourceProvider resourceProvider, RegistryOps.RegistryInfoLookup registryInfoLookup, WritableRegistry registry, Decoder codec, Map, Exception> loadingErrors ) { RegistryDataLoader.NetworkedRegistryData networkedRegistryData = (RegistryDataLoader.NetworkedRegistryData)elements.get(registry.key()); if (networkedRegistryData != null) { RegistryOps registryOps = RegistryOps.create(NbtOps.INSTANCE, registryInfoLookup); RegistryOps registryOps2 = RegistryOps.create(JsonOps.INSTANCE, registryInfoLookup); FileToIdConverter fileToIdConverter = FileToIdConverter.registry(registry.key()); for (PackedRegistryEntry packedRegistryEntry : networkedRegistryData.elements) { ResourceKey resourceKey = ResourceKey.create(registry.key(), packedRegistryEntry.id()); Optional optional = packedRegistryEntry.data(); if (optional.isPresent()) { try { DataResult dataResult = codec.parse(registryOps, (Tag)optional.get()); E object = dataResult.getOrThrow(); registry.register(resourceKey, object, NETWORK_REGISTRATION_INFO); } catch (Exception var16) { loadingErrors.put(resourceKey, new IllegalStateException(String.format(Locale.ROOT, "Failed to parse value %s from server", optional.get()), var16)); } } else { ResourceLocation resourceLocation = fileToIdConverter.idToFile(packedRegistryEntry.id()); try { Resource resource = resourceProvider.getResourceOrThrow(resourceLocation); loadElementFromResource(registry, codec, registryOps2, resourceKey, resource, NETWORK_REGISTRATION_INFO); } catch (Exception var17) { loadingErrors.put(resourceKey, new IllegalStateException("Failed to parse local data", var17)); } } } TagLoader.loadTagsFromNetwork(networkedRegistryData.tags, registry); } } record Loader(RegistryDataLoader.RegistryData data, WritableRegistry registry, Map, Exception> loadingErrors) { public void loadFromResources(ResourceManager resouceManager, RegistryOps.RegistryInfoLookup registryInfoLookup) { RegistryDataLoader.loadContentsFromManager(resouceManager, registryInfoLookup, this.registry, this.data.elementCodec, this.loadingErrors); } public void loadFromNetwork( Map>, RegistryDataLoader.NetworkedRegistryData> elements, ResourceProvider resourceProvider, RegistryOps.RegistryInfoLookup registryInfoLookup ) { RegistryDataLoader.loadContentsFromNetwork(elements, resourceProvider, registryInfoLookup, this.registry, this.data.elementCodec, this.loadingErrors); } } @FunctionalInterface interface LoadingFunction { void apply(RegistryDataLoader.Loader loader, RegistryOps.RegistryInfoLookup registryInfoLookup); } public record NetworkedRegistryData(List elements, NetworkPayload tags) { } public record RegistryData(ResourceKey> key, Codec elementCodec, boolean requiredNonEmpty) { RegistryData(ResourceKey> key, Codec elementCodec) { this(key, elementCodec, false); } RegistryDataLoader.Loader create(Lifecycle registryLifecycle, Map, Exception> loadingErrors) { WritableRegistry writableRegistry = new MappedRegistry<>(this.key, registryLifecycle); return new RegistryDataLoader.Loader<>(this, writableRegistry, loadingErrors); } public void runWithArguments(BiConsumer>, Codec> runner) { runner.accept(this.key, this.elementCodec); } } }