NucleoidMC / fantasy

Library to support creating dimensions at runtime
GNU Lesser General Public License v3.0
98 stars 27 forks source link

The hidden bug in an environment without `fabric-api` mod installed. #58

Open sakurawald opened 1 month ago

sakurawald commented 1 month ago

This is not an actually bug, the code in branch 1.21 works in an environment with fabric-api installed. (Yeah, fabric-api is installed via the jar-in-jar manner).

However, I want to mention that, here is a hidden bug that will be trigger in an environment without the fabric-api: https://github.com/NucleoidMC/fantasy/issues/10

sakurawald commented 1 month ago

Also, without the patches from fabric-api mod, the /save-all command will also failed, due to:

java.lang.IllegalStateException: Trying to access unbound value 'ResourceKey[minecraft:dimension / fuji:2]' from registry net.minecraft.registry.SimpleRegistry$1@476c801e
    at net.minecraft.registry.entry.RegistryEntry$Reference.comp_349(RegistryEntry.java:180) ~[minecraft-merged-1218c1d76b-1.21-net.fabricmc.yarn.1_21.1.21+build.2.jar:?]
    at java.base/java.util.stream.Collectors.lambda$uniqKeysMapAccumulator$1(Collectors.java:180) ~[?:?]
    at java.base/java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169) ~[?:?]
    at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179) ~[?:?]
    at it.unimi.dsi.fastutil.objects.ObjectArrayList$Spliterator.forEachRemaining(ObjectArrayList.java:955) ~[fastutil-8.5.12.jar:?]
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) ~[?:?]
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[?:?]
    at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921) ~[?:?]
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[?:?]
    at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682) ~[?:?]
    at net.minecraft.world.dimension.DimensionOptionsRegistryHolder.<init>(DimensionOptionsRegistryHolder.java:62) ~[minecraft-merged-1218c1d76b-1.21-net.fabricmc.yarn.1_21.1.21+build.2.jar:?]
    at net.minecraft.world.level.WorldGenSettings.encode(WorldGenSettings.java:24) ~[minecraft-merged-1218c1d76b-1.21-net.fabricmc.yarn.1_21.1.21+build.2.jar:?]
    at net.minecraft.world.level.LevelProperties.updateProperties(LevelProperties.java:256) ~[minecraft-merged-1218c1d76b-1.21-net.fabricmc.yarn.1_21.1.21+build.2.jar:?]
    at net.minecraft.world.level.LevelProperties.cloneWorldNbt(LevelProperties.java:233) ~[minecraft-merged-1218c1d76b-1.21-net.fabricmc.yarn.1_21.1.21+build.2.jar:?]
    at net.minecraft.world.level.storage.LevelStorage$Session.backupLevelDataFile(LevelStorage.java:452) ~[minecraft-merged-1218c1d76b-1.21-net.fabricmc.yarn.1_21.1.21+build.2.jar:?]
    at net.minecraft.server.MinecraftServer.save(MinecraftServer.java:563) ~[minecraft-merged-1218c1d76b-1.21-net.fabricmc.yarn.1_21.1.21+build.2.jar:?]
    at net.minecraft.server.MinecraftServer.shutdown(MinecraftServer.java:620) ~[minecraft-merged-1218c1d76b-1.21-net.fabricmc.yarn.1_21.1.21+build.2.jar:?]
    at net.minecraft.server.dedicated.MinecraftDedicatedServer.shutdown(MinecraftDedicatedServer.java:561) ~[minecraft-merged-1218c1d76b-1.21-net.fabricmc.yarn.1_21.1.21+build.2.jar:?]
    at net.minecraft.server.MinecraftServer.runServer(MinecraftServer.java:735) ~[minecraft-merged-1218c1d76b-1.21-net.fabricmc.yarn.1_21.1.21+build.2.jar:?]
    at net.minecraft.server.MinecraftServer.method_29739(MinecraftServer.java:281) ~[minecraft-merged-1218c1d76b-1.21-net.fabricmc.yarn.1_21.1.21+build.2.jar:?]
    at java.base/java.lang.Thread.run(Thread.java:1583) [?:?]

To fix this, still need to add an extra mixin method:

@Mixin(WorldGenSettings.class)
public class WorldGenSettingsMixin {

    @ModifyArg(method = "encode(Lcom/mojang/serialization/DynamicOps;Lnet/minecraft/world/gen/GeneratorOptions;Lnet/minecraft/world/dimension/DimensionOptionsRegistryHolder;)Lcom/mojang/serialization/DataResult;"
        , at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/WorldGenSettings;<init>(Lnet/minecraft/world/gen/GeneratorOptions;Lnet/minecraft/world/dimension/DimensionOptionsRegistryHolder;)V"), index = 1)
    private static @NotNull DimensionOptionsRegistryHolder $wrapWorldGenSettings(DimensionOptionsRegistryHolder original) {
        Map<RegistryKey<DimensionOptions>, DimensionOptions> dimensions = original.comp_1014();
        var saveDimensions = Maps.filterEntries(dimensions, entry -> IDimensionOptions.SAVE_PROPERTIES_PREDICATE.test(entry.getValue()));
        return new DimensionOptionsRegistryHolder(saveDimensions);
    }

    // fix: failed to save world on `/save-all`.
    @ModifyArg(method = "encode(Lcom/mojang/serialization/DynamicOps;Lnet/minecraft/world/gen/GeneratorOptions;Lnet/minecraft/registry/DynamicRegistryManager;)Lcom/mojang/serialization/DataResult;"
        , at = @At(value = "INVOKE", target = "Lnet/minecraft/world/dimension/DimensionOptionsRegistryHolder;<init>(Lnet/minecraft/registry/Registry;)V"), index = 0)
    private static Registry<DimensionOptions> $wrapWorldGenSettings(Registry<DimensionOptions> registry) {
        return new FilteredRegistry<>(registry, IDimensionOptions.SAVE_PROPERTIES_PREDICATE);
    }
}

Although the second method is a delegation to the first method, we still need to filter the DimensionOptions in the second method, it's because before the method delegates to the first method, an excapetion will be thrown and inturrpted the /save-all process.