Querz / mcaselector

A tool to select chunks from Minecraft worlds for deletion or export.
MIT License
3.18k stars 177 forks source link

ForceBlend causes crash "Asking for biomes before we have biomes" (and solution) #377

Open 22dm opened 2 years ago

22dm commented 2 years ago

Describe the bug MCA Selector's ForceBlend feature can crash the game, as I mentioned here. MCA Selector may tag unfinished chunks with "blending_data", causing the game to crash when loading the chunks with the error message "java.lang.IllegalStateException: Asking for biomes before we have biomes".

To Reproduce

  1. Create a new world, the version I use is 1.19.2
  2. Randomly move forward so that some new chunks start to generate, then quit
  3. Use MCA Selector to select all blocks, set ForceBlend
  4. Re-open the world, explore forward, the world will crash soon

Expected behavior Not sure how MCA Selector can do it better, but at least users can find a manual solution from this issue.

Environment (please complete the following information):

Solution TLDR: Select all unfinished blocks (Status != full) in the MCA Selector, then delete them, the world will return to normal.

alexiscoutinho commented 1 year ago

You don't need to restrain yourself to only blending full chunks. The game (tested in 1.19.4) actually tags all visited chunks with a status of noise or after/above when updating worlds through major versions. I tested this ingame and it can also be confirmed by some parts of game code:

public class BlendingDataFix
extends DataFix {
    private final String name;
    private static final Set<String> STATUSES_TO_SKIP_BLENDING = Set.of("minecraft:empty", "minecraft:structure_starts", "minecraft:structure_references", "minecraft:biomes");

    ...

    private static Dynamic<?> updateChunkTag(Dynamic<?> dynamic, OptionalDynamic<?> optionalDynamic) {
        dynamic = dynamic.remove("blending_data");
        boolean bl = "minecraft:overworld".equals(optionalDynamic.get("dimension").asString().result().orElse(""));
        Optional optional = dynamic.get("Status").result();
        if (bl && optional.isPresent()) {
            Dynamic dynamic2;
            String string;
            String string2 = NamespacedSchema.ensureNamespaced(((Dynamic)optional.get()).asString("empty"));
            Optional optional2 = dynamic.get("below_zero_retrogen").result();
            if (!STATUSES_TO_SKIP_BLENDING.contains(string2)) {
                dynamic = BlendingDataFix.updateBlendingData(dynamic, 384, -64);
            } else if (optional2.isPresent() && !STATUSES_TO_SKIP_BLENDING.contains(string = NamespacedSchema.ensureNamespaced((dynamic2 = (Dynamic)optional2.get()).get("target_status").asString("empty")))) {
                dynamic = BlendingDataFix.updateBlendingData(dynamic, 256, 0);
            }
        }
        return dynamic;
    }

    private static Dynamic<?> updateBlendingData(Dynamic<?> dynamic, int n, int n2) {
        return dynamic.set("blending_data", dynamic.createMap(Map.of(dynamic.createString("min_section"), dynamic.createInt(SectionPos.blockToSectionCoord(n2)), dynamic.createString("max_section"), dynamic.createInt(SectionPos.blockToSectionCoord(n2 + n)))));
    }
}
public class ChunkHeightAndBiomeFix
extends DataFix {
    ...
    private static final Set<String> STATUS_IS_OR_AFTER_SURFACE = Set.of("surface", "carvers", "liquid_carvers", "features", "light", "spawn", "heightmaps", "full");
    private static final Set<String> STATUS_IS_OR_AFTER_NOISE = Set.of("noise", "surface", "carvers", "liquid_carvers", "features", "light", "spawn", "heightmaps", "full");
    ...

    ...

    private static Dynamic<?> updateChunkTag(Dynamic<?> dynamic, boolean bl, boolean bl2, boolean bl3, Supplier<ChunkProtoTickListFix.PoorMansPalettedContainer> supplier) {
        ...
        Optional optional = dynamic.get("Status").result();
        if (optional.isPresent() && !"empty".equals(string = (dynamic2 = (Dynamic)optional.get()).asString(""))) {
            dynamic = dynamic.set("blending_data", dynamic.createMap((Map)ImmutableMap.of((Object)dynamic.createString("old_noise"), (Object)dynamic.createBoolean(STATUS_IS_OR_AFTER_NOISE.contains(string)))));
            ChunkProtoTickListFix.PoorMansPalettedContainer poorMansPalettedContainer = supplier.get();
            if (poorMansPalettedContainer != null) {
                BitSet bitSet = new BitSet(256);
                boolean bl4 = string.equals("noise");
                for (int i = 0; i < 16; ++i) {
                    for (int j = 0; j < 16; ++j) {
                        boolean bl5;
                        Dynamic<?> dynamic3 = poorMansPalettedContainer.get(j, 0, i);
                        boolean bl6 = dynamic3 != null && "minecraft:bedrock".equals(dynamic3.get("Name").asString(""));
                        boolean bl7 = bl5 = dynamic3 != null && "minecraft:air".equals(dynamic3.get("Name").asString(""));
                        if (bl5) {
                            bitSet.set(i * 16 + j);
                        }
                        bl4 |= bl6;
                    }
                }
                if (bl4 && bitSet.cardinality() != bitSet.size()) {
                    Dynamic dynamic4 = "full".equals(string) ? dynamic.createString("heightmaps") : dynamic2;
                    dynamic = dynamic.set("below_zero_retrogen", dynamic.createMap((Map)ImmutableMap.of((Object)dynamic.createString("target_status"), (Object)dynamic4, (Object)dynamic.createString("missing_bedrock"), (Object)dynamic.createLongList(LongStream.of(bitSet.toLongArray())))));
                    dynamic = dynamic.set("Status", dynamic.createString("empty"));
                }
                dynamic = dynamic.set("isLightOn", dynamic.createBoolean(false));
            }
        }
        return dynamic;
    }

    ...
}

In fact, you could even tag biomes chunks and not get a crash, but since these still don't have any terrain shape, blending is horrible as they are blended with empty/ugly oceans. MCA Selector should warn when trying to tag biomes chunks and not let users tag structure_starts chunks (I think empty and structure_references chunks also crash, but I haven't tested).

Interestingly, the Nether and End dimensions seem to be skipped for blending, perhaps because their generation hasn't changed much since 1.18:

public class BlendingDataRemoveFromNetherEndFix
extends DataFix {
    ...

    private static Dynamic<?> updateChunkTag(Dynamic<?> dynamic, OptionalDynamic<?> optionalDynamic) {
        boolean bl = "minecraft:overworld".equals(optionalDynamic.get("dimension").asString().result().orElse(""));
        return bl ? dynamic : dynamic.remove("blending_data");
    }
}