mcaselector icon indicating copy to clipboard operation
mcaselector copied to clipboard

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

Open 22dm opened this issue 3 years ago • 1 comments

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):

  • OS: Windows 10 / MacOS 12
  • Java version: 18.0.1
  • Version of MCA Selector: 2.1

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

22dm avatar Aug 08 '22 18:08 22dm

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");
    }
}

alexiscoutinho avatar May 28 '23 17:05 alexiscoutinho