diff --git a/patches/minecraft/net/minecraft/world/storage/SaveFormat.java.patch b/patches/minecraft/net/minecraft/world/storage/SaveFormat.java.patch index 178a734eaa..01539508e0 100644 --- a/patches/minecraft/net/minecraft/world/storage/SaveFormat.java.patch +++ b/patches/minecraft/net/minecraft/world/storage/SaveFormat.java.patch @@ -1,5 +1,14 @@ --- a/net/minecraft/world/storage/SaveFormat.java +++ b/net/minecraft/world/storage/SaveFormat.java +@@ -95,7 +95,7 @@ + } + } + +- Dynamic dynamic1 = p_237259_1_.update(TypeReferences.field_233375_y_, dynamic, p_237259_2_, SharedConstants.func_215069_a().getWorldVersion()); ++ Dynamic dynamic1 = net.minecraftforge.common.ForgeHooks.fixUpDimensionsData(p_237259_1_.update(TypeReferences.field_233375_y_, dynamic, p_237259_2_, SharedConstants.func_215069_a().getWorldVersion())); + DataResult dataresult = DimensionGeneratorSettings.field_236201_a_.parse(dynamic1); + return Pair.of(dataresult.resultOrPartial(Util.func_240982_a_("WorldGenSettings: ", field_215785_a::error)).orElseGet(() -> { + Registry registry = RegistryLookupCodec.func_244331_a(Registry.field_239698_ad_).codec().parse(dynamic1).resultOrPartial(Util.func_240982_a_("Dimension type registry: ", field_215785_a::error)).orElseThrow(() -> { @@ -182,6 +182,10 @@ } diff --git a/src/main/java/net/minecraftforge/common/ForgeHooks.java b/src/main/java/net/minecraftforge/common/ForgeHooks.java index c879d16969..034a419b4b 100644 --- a/src/main/java/net/minecraftforge/common/ForgeHooks.java +++ b/src/main/java/net/minecraftforge/common/ForgeHooks.java @@ -29,8 +29,10 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; import com.google.common.base.Throwables; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; @@ -40,6 +42,10 @@ import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; +import com.mojang.serialization.Dynamic; +import com.mojang.serialization.Lifecycle; import com.mojang.serialization.codecs.RecordCodecBuilder; import javax.annotation.Nonnull; @@ -96,9 +102,16 @@ import net.minecraft.potion.Potion; import net.minecraft.potion.PotionUtils; import net.minecraft.stats.Stats; import net.minecraft.util.math.vector.Vector3d; +import net.minecraft.util.registry.DynamicRegistries; +import net.minecraft.util.registry.Registry; +import net.minecraft.util.registry.SimpleRegistry; +import net.minecraft.util.registry.WorldSettingsImport; import net.minecraft.util.text.*; +import net.minecraft.world.*; import net.minecraft.world.chunk.IChunk; +import net.minecraft.world.gen.DimensionSettings; import net.minecraft.world.gen.feature.structure.Structure; +import net.minecraft.world.gen.settings.DimensionGeneratorSettings; import net.minecraft.world.spawner.AbstractSpawner; import net.minecraft.tileentity.FurnaceTileEntity; import net.minecraft.tileentity.TileEntity; @@ -110,12 +123,6 @@ import net.minecraft.util.math.RayTraceResult; import net.minecraft.util.math.BlockRayTraceResult; import net.minecraft.util.math.EntityRayTraceResult; import net.minecraft.util.text.event.ClickEvent; -import net.minecraft.world.Difficulty; -import net.minecraft.world.GameType; -import net.minecraft.world.IBlockReader; -import net.minecraft.world.IWorld; -import net.minecraft.world.IWorldReader; -import net.minecraft.world.World; import net.minecraft.world.biome.Biome; import net.minecraft.world.biome.BiomeAmbience; import net.minecraft.world.biome.BiomeGenerationSettings; @@ -155,6 +162,7 @@ import net.minecraftforge.event.world.BlockEvent; import net.minecraftforge.event.world.NoteBlockEvent; import net.minecraftforge.eventbus.api.Event.Result; import net.minecraftforge.fluids.FluidAttributes; +import net.minecraftforge.fml.common.ObfuscationReflectionHelper; import net.minecraftforge.fml.packs.ResourcePackLoader; import net.minecraftforge.registries.DataSerializerEntry; import net.minecraftforge.registries.ForgeRegistries; @@ -1283,4 +1291,72 @@ public class ForgeHooks } chunk.setStructureReferences(structureReferences); } + + private static final Set VANILLA_DIMS = Sets.newHashSet("minecraft:overworld", "minecraft:the_nether", "minecraft:the_end"); + private static final String DIMENSIONS_KEY = "dimensions"; + private static final String SEED_KEY = "seed"; + //No to static init! + private static final LazyValue>> CODEC = new LazyValue<>(() -> SimpleRegistry.getSimpleRegistryCodec(Registry.DIMENSION_KEY, Lifecycle.stable(), Dimension.CODEC).xmap(Dimension::func_236062_a_, Function.identity())); + + /** + * Restores previously "deleted" dimensions to the world. + * The {@link LenientUnboundedMapCodec} prevents this from happening, this is to fix any world from before the fix. + */ + public static Dynamic fixUpDimensionsData(Dynamic data) + { + if(!(data.getOps() instanceof WorldSettingsImport)) + return data; + + WorldSettingsImport ops = (WorldSettingsImport) data.getOps(); + Dynamic dymData = data.get(DIMENSIONS_KEY).orElseEmptyMap(); + Dynamic withInjected = dymData.asMapOpt().map(current -> + { + List> currentList = current.map(p -> p.mapFirst(dyn -> dyn.asString().result().orElse("")).mapSecond(Dynamic::getValue)).collect(Collectors.toList()); + Set currentDimNames = currentList.stream().map(Pair::getFirst).collect(Collectors.toSet()); + + // FixUp deleted vanilla dims. + if (!currentDimNames.containsAll(VANILLA_DIMS)) + { + LOGGER.warn("Detected missing vanilla dimensions from the world!"); + DynamicRegistries regs = ObfuscationReflectionHelper.getPrivateValue(WorldSettingsImport.class, ops, "field_240872_d_"); + if (regs == null) // should not happen, but it could after a MC version update. + throw new RuntimeException("Could not access dynamic registries using reflection. " + + "The world was detected to have missing vanilla dimensions and the attempted fix did not work."); + + long seed = data.get(SEED_KEY).get().result().map(d -> d.asLong(0L)).orElse(0L); + Registry biomeReg = regs.getRegistry(Registry.BIOME_KEY); + Registry typeReg = regs.getRegistry(Registry.DIMENSION_TYPE_KEY); + Registry noiseReg = regs.getRegistry(Registry.NOISE_SETTINGS_KEY); + + //Loads the default nether and end + SimpleRegistry dimReg = DimensionType.getDefaultSimpleRegistry(typeReg, biomeReg, noiseReg, seed); + //Loads the default overworld + dimReg = DimensionGeneratorSettings.func_242749_a(typeReg, dimReg, DimensionGeneratorSettings.func_242750_a(biomeReg, noiseReg, seed)); + + // Encode and decode the registry. This adds any dimensions from datapacks (see SimpleRegistryCodec#decode), but only the vanilla overrides are needed. + // This assumes that the datapacks for the vanilla dimensions have not changed since they were "deleted" + // If they did, this will be seen in newly generated chunks. + // Since this is to fix an older world, from before the fixes by forge, there is no way to know the state of the dimension when it was "deleted". + dimReg = CODEC.getValue().encodeStart(ops, dimReg).flatMap(t -> CODEC.getValue().parse(ops, t)).result().orElse(dimReg); + for (String name : VANILLA_DIMS) + { + if (currentDimNames.contains(name)) + continue; + Dimension dim = dimReg.getOrDefault(new ResourceLocation(name)); + if (dim == null) + { + LOGGER.error("The world is missing dimension: " + name + ", but the attempt to re-inject it failed."); + continue; + } + LOGGER.info("Fixing world: re-injected dimension: " + name); + currentList.add(Pair.of(name, Dimension.CODEC.encodeStart(ops, dim).resultOrPartial(s->{}).orElse(ops.empty()))); + } + } + else + return dymData; + + return new Dynamic<>(ops, ops.createMap(currentList.stream().map(p -> p.mapFirst(ops::createString)))); + }).result().orElse(dymData); + return data.set(DIMENSIONS_KEY, withInjected); + } }