minecraft-src/net/minecraft/client/gui/screens/worldselection/WorldOpenFlows.java
2025-07-04 02:00:41 +03:00

467 lines
19 KiB
Java

package net.minecraft.client.gui.screens.worldselection;
import com.mojang.datafixers.util.Pair;
import com.mojang.logging.LogUtils;
import com.mojang.serialization.Dynamic;
import com.mojang.serialization.Lifecycle;
import it.unimi.dsi.fastutil.booleans.BooleanConsumer;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.ChatFormatting;
import net.minecraft.CrashReport;
import net.minecraft.CrashReportCategory;
import net.minecraft.ReportedException;
import net.minecraft.SharedConstants;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.toasts.SystemToast;
import net.minecraft.client.gui.screens.AlertScreen;
import net.minecraft.client.gui.screens.BackupConfirmScreen;
import net.minecraft.client.gui.screens.ConfirmScreen;
import net.minecraft.client.gui.screens.DatapackLoadFailureScreen;
import net.minecraft.client.gui.screens.GenericMessageScreen;
import net.minecraft.client.gui.screens.NoticeWithLinkScreen;
import net.minecraft.client.gui.screens.RecoverWorldDataScreen;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.resources.server.DownloadedPackSource;
import net.minecraft.commands.Commands;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.LayeredRegistryAccess;
import net.minecraft.core.MappedRegistry;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.NbtException;
import net.minecraft.nbt.ReportedNbtException;
import net.minecraft.network.chat.CommonComponents;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.server.RegistryLayer;
import net.minecraft.server.ReloadableServerResources;
import net.minecraft.server.WorldLoader;
import net.minecraft.server.WorldStem;
import net.minecraft.server.packs.repository.PackRepository;
import net.minecraft.server.packs.repository.ServerPacksSource;
import net.minecraft.server.packs.resources.CloseableResourceManager;
import net.minecraft.util.MemoryReserve;
import net.minecraft.world.level.LevelSettings;
import net.minecraft.world.level.WorldDataConfiguration;
import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.levelgen.WorldDimensions;
import net.minecraft.world.level.levelgen.WorldOptions;
import net.minecraft.world.level.storage.LevelDataAndDimensions;
import net.minecraft.world.level.storage.LevelResource;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.level.storage.LevelSummary;
import net.minecraft.world.level.storage.PrimaryLevelData;
import net.minecraft.world.level.storage.WorldData;
import net.minecraft.world.level.storage.LevelSummary.BackupStatus;
import net.minecraft.world.level.validation.ContentValidationException;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
@Environment(EnvType.CLIENT)
public class WorldOpenFlows {
private static final Logger LOGGER = LogUtils.getLogger();
private static final UUID WORLD_PACK_ID = UUID.fromString("640a6a92-b6cb-48a0-b391-831586500359");
private final Minecraft minecraft;
private final LevelStorageSource levelSource;
public WorldOpenFlows(Minecraft minecraft, LevelStorageSource levelSource) {
this.minecraft = minecraft;
this.levelSource = levelSource;
}
public void createFreshLevel(
String levelName, LevelSettings levelSettings, WorldOptions worldOptions, Function<HolderLookup.Provider, WorldDimensions> dimensionGetter, Screen lastScreen
) {
this.minecraft.forceSetScreen(new GenericMessageScreen(Component.translatable("selectWorld.data_read")));
LevelStorageSource.LevelStorageAccess levelStorageAccess = this.createWorldAccess(levelName);
if (levelStorageAccess != null) {
PackRepository packRepository = ServerPacksSource.createPackRepository(levelStorageAccess);
WorldDataConfiguration worldDataConfiguration = levelSettings.getDataConfiguration();
try {
WorldLoader.PackConfig packConfig = new WorldLoader.PackConfig(packRepository, worldDataConfiguration, false, false);
WorldStem worldStem = this.loadWorldDataBlocking(
packConfig,
dataLoadContext -> {
WorldDimensions.Complete complete = ((WorldDimensions)dimensionGetter.apply(dataLoadContext.datapackWorldgen()))
.bake(dataLoadContext.datapackDimensions().lookupOrThrow(Registries.LEVEL_STEM));
return new WorldLoader.DataLoadOutput<>(
new PrimaryLevelData(levelSettings, worldOptions, complete.specialWorldProperty(), complete.lifecycle()), complete.dimensionsRegistryAccess()
);
},
WorldStem::new
);
this.minecraft.doWorldLoad(levelStorageAccess, packRepository, worldStem, true);
} catch (Exception var11) {
LOGGER.warn("Failed to load datapacks, can't proceed with server load", (Throwable)var11);
levelStorageAccess.safeClose();
this.minecraft.setScreen(lastScreen);
}
}
}
@Nullable
private LevelStorageSource.LevelStorageAccess createWorldAccess(String levelName) {
try {
return this.levelSource.validateAndCreateAccess(levelName);
} catch (IOException var3) {
LOGGER.warn("Failed to read level {} data", levelName, var3);
SystemToast.onWorldAccessFailure(this.minecraft, levelName);
this.minecraft.setScreen(null);
return null;
} catch (ContentValidationException var4) {
LOGGER.warn("{}", var4.getMessage());
this.minecraft.setScreen(NoticeWithLinkScreen.createWorldSymlinkWarningScreen(() -> this.minecraft.setScreen(null)));
return null;
}
}
public void createLevelFromExistingSettings(
LevelStorageSource.LevelStorageAccess levelStorage, ReloadableServerResources resources, LayeredRegistryAccess<RegistryLayer> registries, WorldData worldData
) {
PackRepository packRepository = ServerPacksSource.createPackRepository(levelStorage);
CloseableResourceManager closeableResourceManager = new WorldLoader.PackConfig(packRepository, worldData.getDataConfiguration(), false, false)
.createResourceManager()
.getSecond();
this.minecraft.doWorldLoad(levelStorage, packRepository, new WorldStem(closeableResourceManager, resources, registries, worldData), true);
}
public WorldStem loadWorldStem(Dynamic<?> dynamic, boolean safeMode, PackRepository packRepository) throws Exception {
WorldLoader.PackConfig packConfig = LevelStorageSource.getPackConfig(dynamic, packRepository, safeMode);
return this.loadWorldDataBlocking(
packConfig,
dataLoadContext -> {
Registry<LevelStem> registry = dataLoadContext.datapackDimensions().lookupOrThrow(Registries.LEVEL_STEM);
LevelDataAndDimensions levelDataAndDimensions = LevelStorageSource.getLevelDataAndDimensions(
dynamic, dataLoadContext.dataConfiguration(), registry, dataLoadContext.datapackWorldgen()
);
return new WorldLoader.DataLoadOutput<>(levelDataAndDimensions.worldData(), levelDataAndDimensions.dimensions().dimensionsRegistryAccess());
},
WorldStem::new
);
}
public Pair<LevelSettings, WorldCreationContext> recreateWorldData(LevelStorageSource.LevelStorageAccess levelStorage) throws Exception {
PackRepository packRepository = ServerPacksSource.createPackRepository(levelStorage);
Dynamic<?> dynamic = levelStorage.getDataTag();
WorldLoader.PackConfig packConfig = LevelStorageSource.getPackConfig(dynamic, packRepository, false);
@Environment(EnvType.CLIENT)
record Data(LevelSettings levelSettings, WorldOptions options, Registry<LevelStem> existingDimensions) {
}
return this.loadWorldDataBlocking(
packConfig,
dataLoadContext -> {
Registry<LevelStem> registry = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.stable()).freeze();
LevelDataAndDimensions levelDataAndDimensions = LevelStorageSource.getLevelDataAndDimensions(
dynamic, dataLoadContext.dataConfiguration(), registry, dataLoadContext.datapackWorldgen()
);
return new WorldLoader.DataLoadOutput<>(
new Data(
levelDataAndDimensions.worldData().getLevelSettings(),
levelDataAndDimensions.worldData().worldGenOptions(),
levelDataAndDimensions.dimensions().dimensions()
),
dataLoadContext.datapackDimensions()
);
},
(closeableResourceManager, reloadableServerResources, layeredRegistryAccess, arg) -> {
closeableResourceManager.close();
InitialWorldCreationOptions initialWorldCreationOptions = new InitialWorldCreationOptions(WorldCreationUiState.SelectedGameMode.SURVIVAL, Set.of(), null);
return Pair.of(
arg.levelSettings,
new WorldCreationContext(
arg.options,
new WorldDimensions(arg.existingDimensions),
layeredRegistryAccess,
reloadableServerResources,
arg.levelSettings.getDataConfiguration(),
initialWorldCreationOptions
)
);
}
);
}
private <D, R> R loadWorldDataBlocking(
WorldLoader.PackConfig packConfig, WorldLoader.WorldDataSupplier<D> worldDataSupplier, WorldLoader.ResultFactory<D, R> resultFactory
) throws Exception {
WorldLoader.InitConfig initConfig = new WorldLoader.InitConfig(packConfig, Commands.CommandSelection.INTEGRATED, 2);
CompletableFuture<R> completableFuture = WorldLoader.load(initConfig, worldDataSupplier, resultFactory, Util.backgroundExecutor(), this.minecraft);
this.minecraft.managedBlock(completableFuture::isDone);
return (R)completableFuture.get();
}
private void askForBackup(LevelStorageSource.LevelStorageAccess levelStorage, boolean customized, Runnable loadLevel, Runnable onCancel) {
Component component;
Component component2;
if (customized) {
component = Component.translatable("selectWorld.backupQuestion.customized");
component2 = Component.translatable("selectWorld.backupWarning.customized");
} else {
component = Component.translatable("selectWorld.backupQuestion.experimental");
component2 = Component.translatable("selectWorld.backupWarning.experimental");
}
this.minecraft.setScreen(new BackupConfirmScreen(onCancel, (bl, bl2) -> {
if (bl) {
EditWorldScreen.makeBackupAndShowToast(levelStorage);
}
loadLevel.run();
}, component, component2, false));
}
public static void confirmWorldCreation(Minecraft minecraft, CreateWorldScreen screen, Lifecycle lifecycle, Runnable loadWorld, boolean skipWarnings) {
BooleanConsumer booleanConsumer = bl -> {
if (bl) {
loadWorld.run();
} else {
minecraft.setScreen(screen);
}
};
if (skipWarnings || lifecycle == Lifecycle.stable()) {
loadWorld.run();
} else if (lifecycle == Lifecycle.experimental()) {
minecraft.setScreen(
new ConfirmScreen(
booleanConsumer, Component.translatable("selectWorld.warning.experimental.title"), Component.translatable("selectWorld.warning.experimental.question")
)
);
} else {
minecraft.setScreen(
new ConfirmScreen(
booleanConsumer, Component.translatable("selectWorld.warning.deprecated.title"), Component.translatable("selectWorld.warning.deprecated.question")
)
);
}
}
public void openWorld(String worldName, Runnable onFail) {
this.minecraft.forceSetScreen(new GenericMessageScreen(Component.translatable("selectWorld.data_read")));
LevelStorageSource.LevelStorageAccess levelStorageAccess = this.createWorldAccess(worldName);
if (levelStorageAccess != null) {
this.openWorldLoadLevelData(levelStorageAccess, onFail);
}
}
private void openWorldLoadLevelData(LevelStorageSource.LevelStorageAccess levelStorage, Runnable onFail) {
this.minecraft.forceSetScreen(new GenericMessageScreen(Component.translatable("selectWorld.data_read")));
Dynamic<?> dynamic;
LevelSummary levelSummary;
try {
dynamic = levelStorage.getDataTag();
levelSummary = levelStorage.getSummary(dynamic);
} catch (NbtException | ReportedNbtException | IOException var10) {
this.minecraft.setScreen(new RecoverWorldDataScreen(this.minecraft, bl -> {
if (bl) {
this.openWorldLoadLevelData(levelStorage, onFail);
} else {
levelStorage.safeClose();
onFail.run();
}
}, levelStorage));
return;
} catch (OutOfMemoryError var11) {
MemoryReserve.release();
String string = "Ran out of memory trying to read level data of world folder \"" + levelStorage.getLevelId() + "\"";
LOGGER.error(LogUtils.FATAL_MARKER, string);
OutOfMemoryError outOfMemoryError2 = new OutOfMemoryError("Ran out of memory reading level data");
outOfMemoryError2.initCause(var11);
CrashReport crashReport = CrashReport.forThrowable(outOfMemoryError2, string);
CrashReportCategory crashReportCategory = crashReport.addCategory("World details");
crashReportCategory.setDetail("World folder", levelStorage.getLevelId());
throw new ReportedException(crashReport);
}
this.openWorldCheckVersionCompatibility(levelStorage, levelSummary, dynamic, onFail);
}
private void openWorldCheckVersionCompatibility(
LevelStorageSource.LevelStorageAccess levelStorage, LevelSummary levelSummary, Dynamic<?> levelData, Runnable onFail
) {
if (!levelSummary.isCompatible()) {
levelStorage.safeClose();
this.minecraft
.setScreen(
new AlertScreen(
onFail,
Component.translatable("selectWorld.incompatible.title").withColor(-65536),
Component.translatable("selectWorld.incompatible.description", levelSummary.getWorldVersionName())
)
);
} else {
BackupStatus backupStatus = levelSummary.backupStatus();
if (backupStatus.shouldBackup()) {
String string = "selectWorld.backupQuestion." + backupStatus.getTranslationKey();
String string2 = "selectWorld.backupWarning." + backupStatus.getTranslationKey();
MutableComponent mutableComponent = Component.translatable(string);
if (backupStatus.isSevere()) {
mutableComponent.withColor(-2142128);
}
Component component = Component.translatable(string2, levelSummary.getWorldVersionName(), SharedConstants.getCurrentVersion().getName());
this.minecraft.setScreen(new BackupConfirmScreen(() -> {
levelStorage.safeClose();
onFail.run();
}, (bl, bl2) -> {
if (bl) {
EditWorldScreen.makeBackupAndShowToast(levelStorage);
}
this.openWorldLoadLevelStem(levelStorage, levelData, false, onFail);
}, mutableComponent, component, false));
} else {
this.openWorldLoadLevelStem(levelStorage, levelData, false, onFail);
}
}
}
private void openWorldLoadLevelStem(LevelStorageSource.LevelStorageAccess levelStorage, Dynamic<?> levelData, boolean safeMode, Runnable onFail) {
this.minecraft.forceSetScreen(new GenericMessageScreen(Component.translatable("selectWorld.resource_load")));
PackRepository packRepository = ServerPacksSource.createPackRepository(levelStorage);
WorldStem worldStem;
try {
worldStem = this.loadWorldStem(levelData, safeMode, packRepository);
for (LevelStem levelStem : worldStem.registries().compositeAccess().lookupOrThrow(Registries.LEVEL_STEM)) {
levelStem.generator().validate();
}
} catch (Exception var9) {
LOGGER.warn("Failed to load level data or datapacks, can't proceed with server load", (Throwable)var9);
if (!safeMode) {
this.minecraft.setScreen(new DatapackLoadFailureScreen(() -> {
levelStorage.safeClose();
onFail.run();
}, () -> this.openWorldLoadLevelStem(levelStorage, levelData, true, onFail)));
} else {
levelStorage.safeClose();
this.minecraft
.setScreen(
new AlertScreen(
onFail,
Component.translatable("datapackFailure.safeMode.failed.title"),
Component.translatable("datapackFailure.safeMode.failed.description"),
CommonComponents.GUI_BACK,
true
)
);
}
return;
}
this.openWorldCheckWorldStemCompatibility(levelStorage, worldStem, packRepository, onFail);
}
private void openWorldCheckWorldStemCompatibility(
LevelStorageSource.LevelStorageAccess levelStorage, WorldStem worldStem, PackRepository packRepository, Runnable onFail
) {
WorldData worldData = worldStem.worldData();
boolean bl = worldData.worldGenOptions().isOldCustomizedWorld();
boolean bl2 = worldData.worldGenSettingsLifecycle() != Lifecycle.stable();
if (!bl && !bl2) {
this.openWorldLoadBundledResourcePack(levelStorage, worldStem, packRepository, onFail);
} else {
this.askForBackup(levelStorage, bl, () -> this.openWorldLoadBundledResourcePack(levelStorage, worldStem, packRepository, onFail), () -> {
worldStem.close();
levelStorage.safeClose();
onFail.run();
});
}
}
private void openWorldLoadBundledResourcePack(
LevelStorageSource.LevelStorageAccess levelStorage, WorldStem worldStem, PackRepository packRepository, Runnable onFail
) {
DownloadedPackSource downloadedPackSource = this.minecraft.getDownloadedPackSource();
this.loadBundledResourcePack(downloadedPackSource, levelStorage).thenApply(void_ -> true).exceptionallyComposeAsync(throwable -> {
LOGGER.warn("Failed to load pack: ", throwable);
return this.promptBundledPackLoadFailure();
}, this.minecraft).thenAcceptAsync(boolean_ -> {
if (boolean_) {
this.openWorldCheckDiskSpace(levelStorage, worldStem, downloadedPackSource, packRepository, onFail);
} else {
downloadedPackSource.popAll();
worldStem.close();
levelStorage.safeClose();
onFail.run();
}
}, this.minecraft).exceptionally(throwable -> {
this.minecraft.delayCrash(CrashReport.forThrowable(throwable, "Load world"));
return null;
});
}
private void openWorldCheckDiskSpace(
LevelStorageSource.LevelStorageAccess levelStorage, WorldStem worldStem, DownloadedPackSource packSource, PackRepository packRepository, Runnable onFail
) {
if (levelStorage.checkForLowDiskSpace()) {
this.minecraft
.setScreen(
new ConfirmScreen(
bl -> {
if (bl) {
this.openWorldDoLoad(levelStorage, worldStem, packRepository);
} else {
packSource.popAll();
worldStem.close();
levelStorage.safeClose();
onFail.run();
}
},
Component.translatable("selectWorld.warning.lowDiskSpace.title").withStyle(ChatFormatting.RED),
Component.translatable("selectWorld.warning.lowDiskSpace.description"),
CommonComponents.GUI_CONTINUE,
CommonComponents.GUI_BACK
)
);
} else {
this.openWorldDoLoad(levelStorage, worldStem, packRepository);
}
}
private void openWorldDoLoad(LevelStorageSource.LevelStorageAccess levelStorage, WorldStem worldStem, PackRepository packRepository) {
this.minecraft.doWorldLoad(levelStorage, packRepository, worldStem, false);
}
private CompletableFuture<Void> loadBundledResourcePack(DownloadedPackSource packSource, LevelStorageSource.LevelStorageAccess level) {
Path path = level.getLevelPath(LevelResource.MAP_RESOURCE_FILE);
if (Files.exists(path, new LinkOption[0]) && !Files.isDirectory(path, new LinkOption[0])) {
packSource.configureForLocalWorld();
CompletableFuture<Void> completableFuture = packSource.waitForPackFeedback(WORLD_PACK_ID);
packSource.pushLocalPack(WORLD_PACK_ID, path);
return completableFuture;
} else {
return CompletableFuture.completedFuture(null);
}
}
private CompletableFuture<Boolean> promptBundledPackLoadFailure() {
CompletableFuture<Boolean> completableFuture = new CompletableFuture();
this.minecraft
.setScreen(
new ConfirmScreen(
completableFuture::complete,
Component.translatable("multiplayer.texturePrompt.failure.line1"),
Component.translatable("multiplayer.texturePrompt.failure.line2"),
CommonComponents.GUI_PROCEED,
CommonComponents.GUI_CANCEL
)
);
return completableFuture;
}
}