diff --git a/src/main/java/biomesoplenty/client/handler/GuiEventHandler.java b/src/main/java/biomesoplenty/client/handler/GuiEventHandler.java new file mode 100644 index 000000000..59a813a6c --- /dev/null +++ b/src/main/java/biomesoplenty/client/handler/GuiEventHandler.java @@ -0,0 +1,149 @@ +package biomesoplenty.client.handler; + +import biomesoplenty.common.world.BOPWorldTypeUtil; +import biomesoplenty.core.BiomesOPlenty; +import com.mojang.datafixers.util.Function4; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screen.*; +import net.minecraft.command.Commands; +import net.minecraft.resources.*; +import net.minecraft.server.IDynamicRegistries; +import net.minecraft.util.datafix.codec.DatapackCodec; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TranslationTextComponent; +import net.minecraft.world.gen.settings.DimensionGeneratorSettings; +import net.minecraft.world.storage.FolderName; +import net.minecraft.world.storage.IServerConfiguration; +import net.minecraft.world.storage.SaveFormat; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.client.event.GuiOpenEvent; +import net.minecraftforge.client.event.GuiScreenEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +import java.util.Optional; +import java.util.function.Function; + +@OnlyIn(Dist.CLIENT) +@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.FORGE) +public class GuiEventHandler +{ + private static String levelId = null; + private static ConfirmBackupScreen confirmScreen = null; + + @SubscribeEvent + public static void onGuiOpened(GuiOpenEvent event) + { + Screen gui = event.getGui(); + Minecraft mc = Minecraft.getInstance(); + + // Retain the last level that was selected + if (isDataReadScreen(gui)) + { + Screen prevScreen = mc.screen; + + if (prevScreen instanceof WorldSelectionScreen) + { + WorldSelectionScreen worldSelectionScreen = (WorldSelectionScreen)prevScreen; + Optional entry = worldSelectionScreen.list.getSelectedOpt(); + + if (entry.isPresent()) + { + levelId = entry.get().summary.getLevelId(); + } + } + else + { + cleanupGuiTracking(); + } + } + else if (gui instanceof ConfirmBackupScreen && levelId != null) + { + confirmScreen = (ConfirmBackupScreen)gui; + + // Don't show the confirmation screen immediately + event.setCanceled(true); + } + else + { + cleanupGuiTracking(); + } + } + + @SubscribeEvent + public static void onGuiDraw(GuiScreenEvent.DrawScreenEvent event) + { + Screen gui = event.getGui(); + Minecraft mc = Minecraft.getInstance(); + + // We need to check if the bop world type is being used after the save has been unlocked. + // It is still locked during GuiOpenEvent. + if (isDataReadScreen(gui) && levelId != null && confirmScreen != null) + { + // Skip the confirm screen if this is the bop world type + if (isBopWorldType(mc, levelId)) + { + confirmScreen.listener.proceed(false, false); + } + else + { + // Otherwise show the confirm screen + mc.setScreen(confirmScreen); + } + + event.setCanceled(true); + cleanupGuiTracking(); + } + } + + private static void cleanupGuiTracking() + { + levelId = null; + confirmScreen = null; + } + + private static boolean isDataReadScreen(Screen gui) + { + // The data read screen is a dirt message screen. + if (!(gui instanceof DirtMessageScreen)) + return false; + + ITextComponent title = gui.getTitle(); + + // Ensure text component is set as expected + if (!(title instanceof TranslationTextComponent) || !((TranslationTextComponent)title).getKey().equals("selectWorld.data_read")) + { + return false; + } + + return true; + } + + private static boolean isBopWorldType(Minecraft mc, String levelId) + { + try + ( + SaveFormat.LevelSave save = mc.getLevelSource().createAccess(levelId); + Minecraft.PackManager packManager = createPackManager(IDynamicRegistries.builtin(), Minecraft::loadDataPacks, Minecraft::loadWorldData, save); + ) + { + DimensionGeneratorSettings settings = packManager.worldData().worldGenSettings(); + return BOPWorldTypeUtil.isUsingBopWorldType(settings); + } + catch (Exception exception) + { + BiomesOPlenty.logger.warn("Failed to load save.", (Throwable)exception); + return false; + } + } + + private static Minecraft.PackManager createPackManager(IDynamicRegistries.Impl registries, Function dataPackLoader, Function4 worldDataLoader, SaveFormat.LevelSave save) + { + DatapackCodec dataPackCodec = dataPackLoader.apply(save); + ResourcePackList resourcePackList = new ResourcePackList<>(ResourcePackInfo::new, new ServerPackFinder(), new FolderPackFinder(save.getLevelPath(FolderName.DATAPACK_DIR).toFile(), IPackNameDecorator.WORLD)); + DataPackRegistries dataPackRegistries = new DataPackRegistries(Commands.EnvironmentType.INTEGRATED, 2); + IServerConfiguration serverConfiguration = worldDataLoader.apply(save, registries, dataPackRegistries.getResourceManager(), dataPackCodec); + return new Minecraft.PackManager(resourcePackList, dataPackRegistries, serverConfiguration); + } +} diff --git a/src/main/java/biomesoplenty/common/world/BOPWorldTypeUtil.java b/src/main/java/biomesoplenty/common/world/BOPWorldTypeUtil.java index ba617a06e..846339cc2 100644 --- a/src/main/java/biomesoplenty/common/world/BOPWorldTypeUtil.java +++ b/src/main/java/biomesoplenty/common/world/BOPWorldTypeUtil.java @@ -8,28 +8,66 @@ package biomesoplenty.common.world; import biomesoplenty.core.BiomesOPlenty; -import com.google.common.base.MoreObjects; +import com.google.common.collect.Lists; import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.server.dedicated.ServerProperties; +import net.minecraft.util.RegistryKey; +import net.minecraft.world.Dimension; +import net.minecraft.world.DimensionType; +import net.minecraft.world.biome.provider.EndBiomeProvider; +import net.minecraft.world.biome.provider.NetherBiomeProvider; import net.minecraft.world.gen.ChunkGenerator; import net.minecraft.world.gen.DimensionSettings; import net.minecraft.world.gen.NoiseChunkGenerator; import net.minecraft.world.gen.settings.DimensionGeneratorSettings; import net.minecraft.world.storage.ServerWorldInfo; +import java.util.List; import java.util.Locale; -import java.util.Objects; +import java.util.Map; import java.util.Optional; -import java.util.Properties; public class BOPWorldTypeUtil { - private static boolean isUsingBopWorldType(DedicatedServer server) + private static boolean isServerLevelTypeBop(DedicatedServer server) { String levelType = Optional.ofNullable((String)server.getProperties().properties.get("level-type")).map((str) -> str.toLowerCase(Locale.ROOT)).orElse("default"); return levelType.equals("biomesoplenty") || levelType.equals("biomesop"); } + // Derived from Dimension.stable + public static boolean isUsingBopWorldType(DimensionGeneratorSettings settings) + { + List, Dimension>> dimensions = Lists.newArrayList(settings.dimensions().entrySet()); + Map.Entry, Dimension> dimensionEntry0 = dimensions.get(0); + Map.Entry, Dimension> dimensionEntry1 = dimensions.get(1); + Map.Entry, Dimension> dimensionEntry2 = dimensions.get(2); + + // BoP uses the standard dimension layout + if (dimensionEntry0.getKey() != Dimension.OVERWORLD || dimensionEntry1.getKey() != Dimension.NETHER && dimensionEntry2.getKey() != Dimension.END) + { + return false; + } + + Dimension overworld = dimensionEntry0.getValue(); + Dimension nether = dimensionEntry1.getValue(); + Dimension end = dimensionEntry2.getValue(); + + // Ensure noise chunk generators are used in all dimensions + if (!(overworld.generator() instanceof NoiseChunkGenerator) || !(nether.generator() instanceof NoiseChunkGenerator) || !(end.generator() instanceof NoiseChunkGenerator)) + { + return false; + } + + // Ensure our nether and overworld biome providers are being used + if (!(overworld.generator().getBiomeSource() instanceof BOPBiomeProvider) || !(nether.generator().getBiomeSource() instanceof BOPNetherBiomeProvider)) + { + return false; + } + + return true; + } + public static ChunkGenerator createChunkGenerator(long seed) { return new NoiseChunkGenerator(new BOPBiomeProvider(seed), seed, DimensionSettings.Preset.OVERWORLD.settings()); @@ -43,7 +81,7 @@ public class BOPWorldTypeUtil public static void setupForDedicatedServer(DedicatedServer server) { // Ensure we are using the bop world type - if (!isUsingBopWorldType(server)) + if (!isServerLevelTypeBop(server)) return; ServerProperties properties = server.getProperties(); diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index 51b1d9c94..315b467d8 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -4,6 +4,13 @@ public net.minecraft.world.gen.feature.TreeFeature *() public-f net.minecraft.client.gui.screen.BiomeGeneratorTypeScreens * public net.minecraft.client.gui.screen.BiomeGeneratorTypeScreens (Ljava/lang/String;)V public net.minecraft.block.Blocks *() +public net.minecraft.client.Minecraft$PackManager *() + +# Set worldtype as default and skip the confirm backup screen +public-f net.minecraft.client.gui.screen.ConfirmBackupScreen * +public-f net.minecraft.client.gui.screen.WorldOptionsScreen * +public net.minecraft.client.gui.screen.WorldSelectionScreen * +public net.minecraft.client.gui.screen.WorldSelectionList$Entry * # server.properties world type hackery public-f net.minecraft.server.dedicated.ServerProperties *