864 lines
37 KiB
Java
864 lines
37 KiB
Java
package net.minecraft.client.gui.screens.worldselection;
|
|
|
|
import com.google.common.collect.ImmutableList;
|
|
import com.google.gson.JsonElement;
|
|
import com.mojang.datafixers.util.Pair;
|
|
import com.mojang.logging.LogUtils;
|
|
import com.mojang.serialization.DataResult;
|
|
import com.mojang.serialization.DynamicOps;
|
|
import com.mojang.serialization.JsonOps;
|
|
import com.mojang.serialization.Lifecycle;
|
|
import java.io.IOException;
|
|
import java.io.UncheckedIOException;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.LinkOption;
|
|
import java.nio.file.Path;
|
|
import java.util.Comparator;
|
|
import java.util.List;
|
|
import java.util.Optional;
|
|
import java.util.OptionalLong;
|
|
import java.util.Set;
|
|
import java.util.concurrent.CompletableFuture;
|
|
import java.util.function.Consumer;
|
|
import java.util.function.Function;
|
|
import java.util.stream.Stream;
|
|
import net.fabricmc.api.EnvType;
|
|
import net.fabricmc.api.Environment;
|
|
import net.minecraft.ChatFormatting;
|
|
import net.minecraft.FileUtil;
|
|
import net.minecraft.SharedConstants;
|
|
import net.minecraft.Util;
|
|
import net.minecraft.client.Minecraft;
|
|
import net.minecraft.client.gui.GuiGraphics;
|
|
import net.minecraft.client.gui.components.AbstractWidget;
|
|
import net.minecraft.client.gui.components.Button;
|
|
import net.minecraft.client.gui.components.CycleButton;
|
|
import net.minecraft.client.gui.components.EditBox;
|
|
import net.minecraft.client.gui.components.Tooltip;
|
|
import net.minecraft.client.gui.components.CycleButton.ValueListSupplier;
|
|
import net.minecraft.client.gui.components.tabs.GridLayoutTab;
|
|
import net.minecraft.client.gui.components.tabs.TabManager;
|
|
import net.minecraft.client.gui.components.tabs.TabNavigationBar;
|
|
import net.minecraft.client.gui.components.toasts.SystemToast;
|
|
import net.minecraft.client.gui.layouts.CommonLayouts;
|
|
import net.minecraft.client.gui.layouts.HeaderAndFooterLayout;
|
|
import net.minecraft.client.gui.layouts.LayoutSettings;
|
|
import net.minecraft.client.gui.layouts.LinearLayout;
|
|
import net.minecraft.client.gui.layouts.GridLayout.RowHelper;
|
|
import net.minecraft.client.gui.navigation.ScreenRectangle;
|
|
import net.minecraft.client.gui.screens.ConfirmScreen;
|
|
import net.minecraft.client.gui.screens.GenericMessageScreen;
|
|
import net.minecraft.client.gui.screens.Screen;
|
|
import net.minecraft.client.gui.screens.packs.PackSelectionScreen;
|
|
import net.minecraft.client.gui.screens.worldselection.CreateWorldScreen.WorldTab.1;
|
|
import net.minecraft.client.gui.screens.worldselection.CreateWorldScreen.WorldTab.2;
|
|
import net.minecraft.client.gui.screens.worldselection.SwitchGrid.Builder;
|
|
import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState.SelectedGameMode;
|
|
import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState.WorldTypeEntry;
|
|
import net.minecraft.client.renderer.RenderType;
|
|
import net.minecraft.commands.Commands;
|
|
import net.minecraft.core.LayeredRegistryAccess;
|
|
import net.minecraft.core.registries.Registries;
|
|
import net.minecraft.network.chat.CommonComponents;
|
|
import net.minecraft.network.chat.Component;
|
|
import net.minecraft.network.chat.MutableComponent;
|
|
import net.minecraft.resources.ResourceKey;
|
|
import net.minecraft.resources.ResourceLocation;
|
|
import net.minecraft.server.RegistryLayer;
|
|
import net.minecraft.server.WorldLoader;
|
|
import net.minecraft.server.WorldLoader.DataLoadContext;
|
|
import net.minecraft.server.WorldLoader.DataLoadOutput;
|
|
import net.minecraft.server.WorldLoader.InitConfig;
|
|
import net.minecraft.server.WorldLoader.PackConfig;
|
|
import net.minecraft.server.packs.repository.PackRepository;
|
|
import net.minecraft.server.packs.repository.ServerPacksSource;
|
|
import net.minecraft.world.Difficulty;
|
|
import net.minecraft.world.flag.FeatureFlagSet;
|
|
import net.minecraft.world.flag.FeatureFlags;
|
|
import net.minecraft.world.level.DataPackConfig;
|
|
import net.minecraft.world.level.GameRules;
|
|
import net.minecraft.world.level.GameType;
|
|
import net.minecraft.world.level.LevelSettings;
|
|
import net.minecraft.world.level.WorldDataConfiguration;
|
|
import net.minecraft.world.level.levelgen.WorldGenSettings;
|
|
import net.minecraft.world.level.levelgen.WorldOptions;
|
|
import net.minecraft.world.level.levelgen.WorldDimensions.Complete;
|
|
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorPresets;
|
|
import net.minecraft.world.level.levelgen.presets.WorldPreset;
|
|
import net.minecraft.world.level.levelgen.presets.WorldPresets;
|
|
import net.minecraft.world.level.storage.LevelResource;
|
|
import net.minecraft.world.level.storage.LevelStorageSource;
|
|
import net.minecraft.world.level.storage.PrimaryLevelData;
|
|
import net.minecraft.world.level.storage.WorldData;
|
|
import net.minecraft.world.level.validation.DirectoryValidator;
|
|
import org.apache.commons.lang3.mutable.MutableObject;
|
|
import org.jetbrains.annotations.Nullable;
|
|
import org.slf4j.Logger;
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
public class CreateWorldScreen extends Screen {
|
|
private static final int GROUP_BOTTOM = 1;
|
|
private static final int TAB_COLUMN_WIDTH = 210;
|
|
private static final Logger LOGGER = LogUtils.getLogger();
|
|
private static final String TEMP_WORLD_PREFIX = "mcworld-";
|
|
static final Component GAME_MODEL_LABEL = Component.translatable("selectWorld.gameMode");
|
|
static final Component NAME_LABEL = Component.translatable("selectWorld.enterName");
|
|
static final Component EXPERIMENTS_LABEL = Component.translatable("selectWorld.experiments");
|
|
static final Component ALLOW_COMMANDS_INFO = Component.translatable("selectWorld.allowCommands.info");
|
|
private static final Component PREPARING_WORLD_DATA = Component.translatable("createWorld.preparing");
|
|
private static final int HORIZONTAL_BUTTON_SPACING = 10;
|
|
private static final int VERTICAL_BUTTON_SPACING = 8;
|
|
public static final ResourceLocation TAB_HEADER_BACKGROUND = ResourceLocation.withDefaultNamespace("textures/gui/tab_header_background.png");
|
|
private final HeaderAndFooterLayout layout = new HeaderAndFooterLayout(this);
|
|
final WorldCreationUiState uiState;
|
|
private final TabManager tabManager = new TabManager(guiEventListener -> {
|
|
AbstractWidget var10000 = this.addRenderableWidget(guiEventListener);
|
|
}, guiEventListener -> this.removeWidget(guiEventListener));
|
|
private boolean recreated;
|
|
private final DirectoryValidator packValidator;
|
|
private final CreateWorldCallback createWorldCallback;
|
|
@Nullable
|
|
private final Screen lastScreen;
|
|
@Nullable
|
|
private Path tempDataPackDir;
|
|
@Nullable
|
|
private PackRepository tempDataPackRepository;
|
|
@Nullable
|
|
private TabNavigationBar tabNavigationBar;
|
|
|
|
public static void openFresh(Minecraft minecraft, @Nullable Screen lastScreen) {
|
|
openFresh(
|
|
minecraft,
|
|
lastScreen,
|
|
(createWorldScreen, layeredRegistryAccess, primaryLevelData, path) -> createWorldScreen.createNewWorld(layeredRegistryAccess, primaryLevelData)
|
|
);
|
|
}
|
|
|
|
public static void openFresh(Minecraft minecraft, @Nullable Screen lastScreen, CreateWorldCallback callback) {
|
|
WorldCreationContextMapper worldCreationContextMapper = (reloadableServerResources, layeredRegistryAccess, dataPackReloadCookie) -> new WorldCreationContext(
|
|
dataPackReloadCookie.worldGenSettings(), layeredRegistryAccess, reloadableServerResources, dataPackReloadCookie.dataConfiguration()
|
|
);
|
|
Function<DataLoadContext, WorldGenSettings> function = dataLoadContext -> new WorldGenSettings(
|
|
WorldOptions.defaultWithRandomSeed(), WorldPresets.createNormalWorldDimensions(dataLoadContext.datapackWorldgen())
|
|
);
|
|
openCreateWorldScreen(minecraft, lastScreen, function, worldCreationContextMapper, WorldPresets.NORMAL, callback);
|
|
}
|
|
|
|
public static void testWorld(Minecraft minecraft, @Nullable Screen lastScreen) {
|
|
WorldCreationContextMapper worldCreationContextMapper = (reloadableServerResources, layeredRegistryAccess, dataPackReloadCookie) -> new WorldCreationContext(
|
|
dataPackReloadCookie.worldGenSettings().options(),
|
|
dataPackReloadCookie.worldGenSettings().dimensions(),
|
|
layeredRegistryAccess,
|
|
reloadableServerResources,
|
|
dataPackReloadCookie.dataConfiguration(),
|
|
new InitialWorldCreationOptions(
|
|
SelectedGameMode.CREATIVE,
|
|
Set.of(GameRules.RULE_DAYLIGHT, GameRules.RULE_WEATHER_CYCLE, GameRules.RULE_DOMOBSPAWNING),
|
|
FlatLevelGeneratorPresets.REDSTONE_READY
|
|
)
|
|
);
|
|
Function<DataLoadContext, WorldGenSettings> function = dataLoadContext -> new WorldGenSettings(
|
|
WorldOptions.testWorldWithRandomSeed(), WorldPresets.createFlatWorldDimensions(dataLoadContext.datapackWorldgen())
|
|
);
|
|
openCreateWorldScreen(
|
|
minecraft,
|
|
lastScreen,
|
|
function,
|
|
worldCreationContextMapper,
|
|
WorldPresets.FLAT,
|
|
(createWorldScreen, layeredRegistryAccess, primaryLevelData, path) -> createWorldScreen.createNewWorld(layeredRegistryAccess, primaryLevelData)
|
|
);
|
|
}
|
|
|
|
private static void openCreateWorldScreen(
|
|
Minecraft minecraft,
|
|
@Nullable Screen lastScreen,
|
|
Function<DataLoadContext, WorldGenSettings> worldGenSettingsGetter,
|
|
WorldCreationContextMapper creationContextMapper,
|
|
ResourceKey<WorldPreset> preset,
|
|
CreateWorldCallback createWorldCallback
|
|
) {
|
|
queueLoadScreen(minecraft, PREPARING_WORLD_DATA);
|
|
PackRepository packRepository = new PackRepository(new ServerPacksSource(minecraft.directoryValidator()));
|
|
WorldDataConfiguration worldDataConfiguration = SharedConstants.IS_RUNNING_IN_IDE
|
|
? new WorldDataConfiguration(new DataPackConfig(List.of("vanilla", "tests"), List.of()), FeatureFlags.DEFAULT_FLAGS)
|
|
: WorldDataConfiguration.DEFAULT;
|
|
InitConfig initConfig = createDefaultLoadConfig(packRepository, worldDataConfiguration);
|
|
CompletableFuture<WorldCreationContext> completableFuture = WorldLoader.load(
|
|
initConfig,
|
|
dataLoadContext -> new DataLoadOutput<>(
|
|
new DataPackReloadCookie((WorldGenSettings)worldGenSettingsGetter.apply(dataLoadContext), dataLoadContext.dataConfiguration()),
|
|
dataLoadContext.datapackDimensions()
|
|
),
|
|
(closeableResourceManager, reloadableServerResources, layeredRegistryAccess, dataPackReloadCookie) -> {
|
|
closeableResourceManager.close();
|
|
return creationContextMapper.apply(reloadableServerResources, layeredRegistryAccess, dataPackReloadCookie);
|
|
},
|
|
Util.backgroundExecutor(),
|
|
minecraft
|
|
);
|
|
minecraft.managedBlock(completableFuture::isDone);
|
|
minecraft.setScreen(
|
|
new CreateWorldScreen(minecraft, lastScreen, (WorldCreationContext)completableFuture.join(), Optional.of(preset), OptionalLong.empty(), createWorldCallback)
|
|
);
|
|
}
|
|
|
|
public static CreateWorldScreen createFromExisting(
|
|
Minecraft minecraft, @Nullable Screen lastScreen, LevelSettings levelSettings, WorldCreationContext context, @Nullable Path tempDataPackDir
|
|
) {
|
|
CreateWorldScreen createWorldScreen = new CreateWorldScreen(
|
|
minecraft,
|
|
lastScreen,
|
|
context,
|
|
WorldPresets.fromSettings(context.selectedDimensions()),
|
|
OptionalLong.of(context.options().seed()),
|
|
(createWorldScreenx, layeredRegistryAccess, primaryLevelData, path) -> createWorldScreenx.createNewWorld(layeredRegistryAccess, primaryLevelData)
|
|
);
|
|
createWorldScreen.recreated = true;
|
|
createWorldScreen.uiState.setName(levelSettings.levelName());
|
|
createWorldScreen.uiState.setAllowCommands(levelSettings.allowCommands());
|
|
createWorldScreen.uiState.setDifficulty(levelSettings.difficulty());
|
|
createWorldScreen.uiState.getGameRules().assignFrom(levelSettings.gameRules(), null);
|
|
if (levelSettings.hardcore()) {
|
|
createWorldScreen.uiState.setGameMode(SelectedGameMode.HARDCORE);
|
|
} else if (levelSettings.gameType().isSurvival()) {
|
|
createWorldScreen.uiState.setGameMode(SelectedGameMode.SURVIVAL);
|
|
} else if (levelSettings.gameType().isCreative()) {
|
|
createWorldScreen.uiState.setGameMode(SelectedGameMode.CREATIVE);
|
|
}
|
|
|
|
createWorldScreen.tempDataPackDir = tempDataPackDir;
|
|
return createWorldScreen;
|
|
}
|
|
|
|
private CreateWorldScreen(
|
|
Minecraft minecraft,
|
|
@Nullable Screen lastScreen,
|
|
WorldCreationContext context,
|
|
Optional<ResourceKey<WorldPreset>> preset,
|
|
OptionalLong seed,
|
|
CreateWorldCallback createWorldCallback
|
|
) {
|
|
super(Component.translatable("selectWorld.create"));
|
|
this.lastScreen = lastScreen;
|
|
this.packValidator = minecraft.directoryValidator();
|
|
this.createWorldCallback = createWorldCallback;
|
|
this.uiState = new WorldCreationUiState(minecraft.getLevelSource().getBaseDir(), context, preset, seed);
|
|
}
|
|
|
|
public WorldCreationUiState getUiState() {
|
|
return this.uiState;
|
|
}
|
|
|
|
@Override
|
|
protected void init() {
|
|
this.tabNavigationBar = TabNavigationBar.builder(this.tabManager, this.width)
|
|
.addTabs(new CreateWorldScreen.GameTab(), new CreateWorldScreen.WorldTab(), new CreateWorldScreen.MoreTab())
|
|
.build();
|
|
this.addRenderableWidget(this.tabNavigationBar);
|
|
LinearLayout linearLayout = this.layout.addToFooter(LinearLayout.horizontal().spacing(8));
|
|
linearLayout.addChild(Button.builder(Component.translatable("selectWorld.create"), button -> this.onCreate()).build());
|
|
linearLayout.addChild(Button.builder(CommonComponents.GUI_CANCEL, button -> this.popScreen()).build());
|
|
this.layout.visitWidgets(abstractWidget -> {
|
|
abstractWidget.setTabOrderGroup(1);
|
|
this.addRenderableWidget(abstractWidget);
|
|
});
|
|
this.tabNavigationBar.selectTab(0, false);
|
|
this.uiState.onChanged();
|
|
this.repositionElements();
|
|
}
|
|
|
|
@Override
|
|
protected void setInitialFocus() {
|
|
}
|
|
|
|
@Override
|
|
public void repositionElements() {
|
|
if (this.tabNavigationBar != null) {
|
|
this.tabNavigationBar.setWidth(this.width);
|
|
this.tabNavigationBar.arrangeElements();
|
|
int i = this.tabNavigationBar.getRectangle().bottom();
|
|
ScreenRectangle screenRectangle = new ScreenRectangle(0, i, this.width, this.height - this.layout.getFooterHeight() - i);
|
|
this.tabManager.setTabArea(screenRectangle);
|
|
this.layout.setHeaderHeight(i);
|
|
this.layout.arrangeElements();
|
|
}
|
|
}
|
|
|
|
private static void queueLoadScreen(Minecraft minecraft, Component title) {
|
|
minecraft.forceSetScreen(new GenericMessageScreen(title));
|
|
}
|
|
|
|
private void onCreate() {
|
|
WorldCreationContext worldCreationContext = this.uiState.getSettings();
|
|
Complete complete = worldCreationContext.selectedDimensions().bake(worldCreationContext.datapackDimensions());
|
|
LayeredRegistryAccess<RegistryLayer> layeredRegistryAccess = worldCreationContext.worldgenRegistries()
|
|
.replaceFrom(RegistryLayer.DIMENSIONS, complete.dimensionsRegistryAccess());
|
|
Lifecycle lifecycle = FeatureFlags.isExperimental(worldCreationContext.dataConfiguration().enabledFeatures()) ? Lifecycle.experimental() : Lifecycle.stable();
|
|
Lifecycle lifecycle2 = layeredRegistryAccess.compositeAccess().allRegistriesLifecycle();
|
|
Lifecycle lifecycle3 = lifecycle2.add(lifecycle);
|
|
boolean bl = !this.recreated && lifecycle2 == Lifecycle.stable();
|
|
LevelSettings levelSettings = this.createLevelSettings(complete.specialWorldProperty() == PrimaryLevelData.SpecialWorldProperty.DEBUG);
|
|
PrimaryLevelData primaryLevelData = new PrimaryLevelData(levelSettings, this.uiState.getSettings().options(), complete.specialWorldProperty(), lifecycle3);
|
|
WorldOpenFlows.confirmWorldCreation(this.minecraft, this, lifecycle3, () -> this.createWorldAndCleanup(layeredRegistryAccess, primaryLevelData), bl);
|
|
}
|
|
|
|
private void createWorldAndCleanup(LayeredRegistryAccess<RegistryLayer> registryAccess, PrimaryLevelData levelData) {
|
|
boolean bl = this.createWorldCallback.create(this, registryAccess, levelData, this.tempDataPackDir);
|
|
this.removeTempDataPackDir();
|
|
if (!bl) {
|
|
this.popScreen();
|
|
}
|
|
}
|
|
|
|
private boolean createNewWorld(LayeredRegistryAccess<RegistryLayer> registryAccess, WorldData worldData) {
|
|
String string = this.uiState.getTargetFolder();
|
|
WorldCreationContext worldCreationContext = this.uiState.getSettings();
|
|
queueLoadScreen(this.minecraft, PREPARING_WORLD_DATA);
|
|
Optional<LevelStorageSource.LevelStorageAccess> optional = createNewWorldDirectory(this.minecraft, string, this.tempDataPackDir);
|
|
if (optional.isEmpty()) {
|
|
SystemToast.onPackCopyFailure(this.minecraft, string);
|
|
return false;
|
|
} else {
|
|
this.minecraft
|
|
.createWorldOpenFlows()
|
|
.createLevelFromExistingSettings((LevelStorageSource.LevelStorageAccess)optional.get(), worldCreationContext.dataPackResources(), registryAccess, worldData);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
private LevelSettings createLevelSettings(boolean debug) {
|
|
String string = this.uiState.getName().trim();
|
|
if (debug) {
|
|
GameRules gameRules = new GameRules(WorldDataConfiguration.DEFAULT.enabledFeatures());
|
|
gameRules.getRule(GameRules.RULE_DAYLIGHT).set(false, null);
|
|
return new LevelSettings(string, GameType.SPECTATOR, false, Difficulty.PEACEFUL, true, gameRules, WorldDataConfiguration.DEFAULT);
|
|
} else {
|
|
return new LevelSettings(
|
|
string,
|
|
this.uiState.getGameMode().gameType,
|
|
this.uiState.isHardcore(),
|
|
this.uiState.getDifficulty(),
|
|
this.uiState.isAllowCommands(),
|
|
this.uiState.getGameRules(),
|
|
this.uiState.getSettings().dataConfiguration()
|
|
);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
|
|
if (this.tabNavigationBar.keyPressed(keyCode)) {
|
|
return true;
|
|
} else if (super.keyPressed(keyCode, scanCode, modifiers)) {
|
|
return true;
|
|
} else if (keyCode != 257 && keyCode != 335) {
|
|
return false;
|
|
} else {
|
|
this.onCreate();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onClose() {
|
|
this.popScreen();
|
|
}
|
|
|
|
public void popScreen() {
|
|
this.minecraft.setScreen(this.lastScreen);
|
|
this.removeTempDataPackDir();
|
|
}
|
|
|
|
@Override
|
|
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {
|
|
super.render(guiGraphics, mouseX, mouseY, partialTick);
|
|
guiGraphics.blit(RenderType::guiTextured, Screen.FOOTER_SEPARATOR, 0, this.height - this.layout.getFooterHeight() - 2, 0.0F, 0.0F, this.width, 2, 32, 2);
|
|
}
|
|
|
|
@Override
|
|
protected void renderMenuBackground(GuiGraphics partialTick) {
|
|
partialTick.blit(RenderType::guiTextured, TAB_HEADER_BACKGROUND, 0, 0, 0.0F, 0.0F, this.width, this.layout.getHeaderHeight(), 16, 16);
|
|
this.renderMenuBackground(partialTick, 0, this.layout.getHeaderHeight(), this.width, this.height);
|
|
}
|
|
|
|
@Nullable
|
|
private Path getOrCreateTempDataPackDir() {
|
|
if (this.tempDataPackDir == null) {
|
|
try {
|
|
this.tempDataPackDir = Files.createTempDirectory("mcworld-");
|
|
} catch (IOException var2) {
|
|
LOGGER.warn("Failed to create temporary dir", (Throwable)var2);
|
|
SystemToast.onPackCopyFailure(this.minecraft, this.uiState.getTargetFolder());
|
|
this.popScreen();
|
|
}
|
|
}
|
|
|
|
return this.tempDataPackDir;
|
|
}
|
|
|
|
void openExperimentsScreen(WorldDataConfiguration worldDataConfiguration) {
|
|
Pair<Path, PackRepository> pair = this.getDataPackSelectionSettings(worldDataConfiguration);
|
|
if (pair != null) {
|
|
this.minecraft
|
|
.setScreen(new ExperimentsScreen(this, pair.getSecond(), packRepository -> this.tryApplyNewDataPacks(packRepository, false, this::openExperimentsScreen)));
|
|
}
|
|
}
|
|
|
|
void openDataPackSelectionScreen(WorldDataConfiguration worldDataConfiguration) {
|
|
Pair<Path, PackRepository> pair = this.getDataPackSelectionSettings(worldDataConfiguration);
|
|
if (pair != null) {
|
|
this.minecraft
|
|
.setScreen(
|
|
new PackSelectionScreen(
|
|
pair.getSecond(),
|
|
packRepository -> this.tryApplyNewDataPacks(packRepository, true, this::openDataPackSelectionScreen),
|
|
pair.getFirst(),
|
|
Component.translatable("dataPack.title")
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
private void tryApplyNewDataPacks(PackRepository packRepository, boolean shouldConfirm, Consumer<WorldDataConfiguration> callback) {
|
|
List<String> list = ImmutableList.copyOf(packRepository.getSelectedIds());
|
|
List<String> list2 = (List<String>)packRepository.getAvailableIds()
|
|
.stream()
|
|
.filter(string -> !list.contains(string))
|
|
.collect(ImmutableList.toImmutableList());
|
|
WorldDataConfiguration worldDataConfiguration = new WorldDataConfiguration(
|
|
new DataPackConfig(list, list2), this.uiState.getSettings().dataConfiguration().enabledFeatures()
|
|
);
|
|
if (this.uiState.tryUpdateDataConfiguration(worldDataConfiguration)) {
|
|
this.minecraft.setScreen(this);
|
|
} else {
|
|
FeatureFlagSet featureFlagSet = packRepository.getRequestedFeatureFlags();
|
|
if (FeatureFlags.isExperimental(featureFlagSet) && shouldConfirm) {
|
|
this.minecraft.setScreen(new ConfirmExperimentalFeaturesScreen(packRepository.getSelectedPacks(), bl -> {
|
|
if (bl) {
|
|
this.applyNewPackConfig(packRepository, worldDataConfiguration, callback);
|
|
} else {
|
|
callback.accept(this.uiState.getSettings().dataConfiguration());
|
|
}
|
|
}));
|
|
} else {
|
|
this.applyNewPackConfig(packRepository, worldDataConfiguration, callback);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void applyNewPackConfig(PackRepository packRepository, WorldDataConfiguration worldDataConfiguration, Consumer<WorldDataConfiguration> callback) {
|
|
this.minecraft.forceSetScreen(new GenericMessageScreen(Component.translatable("dataPack.validation.working")));
|
|
InitConfig initConfig = createDefaultLoadConfig(packRepository, worldDataConfiguration);
|
|
WorldLoader.<DataPackReloadCookie, WorldCreationContext>load(
|
|
initConfig,
|
|
dataLoadContext -> {
|
|
if (dataLoadContext.datapackWorldgen().lookupOrThrow(Registries.WORLD_PRESET).listElements().findAny().isEmpty()) {
|
|
throw new IllegalStateException("Needs at least one world preset to continue");
|
|
} else if (dataLoadContext.datapackWorldgen().lookupOrThrow(Registries.BIOME).listElements().findAny().isEmpty()) {
|
|
throw new IllegalStateException("Needs at least one biome continue");
|
|
} else {
|
|
WorldCreationContext worldCreationContext = this.uiState.getSettings();
|
|
DynamicOps<JsonElement> dynamicOps = worldCreationContext.worldgenLoadContext().createSerializationContext(JsonOps.INSTANCE);
|
|
DataResult<JsonElement> dataResult = WorldGenSettings.encode(dynamicOps, worldCreationContext.options(), worldCreationContext.selectedDimensions())
|
|
.setLifecycle(Lifecycle.stable());
|
|
DynamicOps<JsonElement> dynamicOps2 = dataLoadContext.datapackWorldgen().createSerializationContext(JsonOps.INSTANCE);
|
|
WorldGenSettings worldGenSettings = dataResult.<WorldGenSettings>flatMap(jsonElement -> WorldGenSettings.CODEC.parse(dynamicOps2, jsonElement))
|
|
.getOrThrow(string -> new IllegalStateException("Error parsing worldgen settings after loading data packs: " + string));
|
|
return new DataLoadOutput<>(new DataPackReloadCookie(worldGenSettings, dataLoadContext.dataConfiguration()), dataLoadContext.datapackDimensions());
|
|
}
|
|
},
|
|
(closeableResourceManager, reloadableServerResources, layeredRegistryAccess, dataPackReloadCookie) -> {
|
|
closeableResourceManager.close();
|
|
return new WorldCreationContext(
|
|
dataPackReloadCookie.worldGenSettings(), layeredRegistryAccess, reloadableServerResources, dataPackReloadCookie.dataConfiguration()
|
|
);
|
|
},
|
|
Util.backgroundExecutor(),
|
|
this.minecraft
|
|
)
|
|
.thenApply(worldCreationContext -> {
|
|
worldCreationContext.validate();
|
|
return worldCreationContext;
|
|
})
|
|
.thenAcceptAsync(this.uiState::setSettings, this.minecraft)
|
|
.handleAsync(
|
|
(void_, throwable) -> {
|
|
if (throwable != null) {
|
|
LOGGER.warn("Failed to validate datapack", throwable);
|
|
this.minecraft
|
|
.setScreen(
|
|
new ConfirmScreen(
|
|
bl -> {
|
|
if (bl) {
|
|
callback.accept(this.uiState.getSettings().dataConfiguration());
|
|
} else {
|
|
callback.accept(WorldDataConfiguration.DEFAULT);
|
|
}
|
|
},
|
|
Component.translatable("dataPack.validation.failed"),
|
|
CommonComponents.EMPTY,
|
|
Component.translatable("dataPack.validation.back"),
|
|
Component.translatable("dataPack.validation.reset")
|
|
)
|
|
);
|
|
} else {
|
|
this.minecraft.setScreen(this);
|
|
}
|
|
|
|
return null;
|
|
},
|
|
this.minecraft
|
|
);
|
|
}
|
|
|
|
private static InitConfig createDefaultLoadConfig(PackRepository packRepository, WorldDataConfiguration initialDataConfig) {
|
|
PackConfig packConfig = new PackConfig(packRepository, initialDataConfig, false, true);
|
|
return new InitConfig(packConfig, Commands.CommandSelection.INTEGRATED, 2);
|
|
}
|
|
|
|
private void removeTempDataPackDir() {
|
|
if (this.tempDataPackDir != null && Files.exists(this.tempDataPackDir, new LinkOption[0])) {
|
|
try {
|
|
Stream<Path> stream = Files.walk(this.tempDataPackDir);
|
|
|
|
try {
|
|
stream.sorted(Comparator.reverseOrder()).forEach(path -> {
|
|
try {
|
|
Files.delete(path);
|
|
} catch (IOException var2) {
|
|
LOGGER.warn("Failed to remove temporary file {}", path, var2);
|
|
}
|
|
});
|
|
} catch (Throwable var5) {
|
|
if (stream != null) {
|
|
try {
|
|
stream.close();
|
|
} catch (Throwable var4) {
|
|
var5.addSuppressed(var4);
|
|
}
|
|
}
|
|
|
|
throw var5;
|
|
}
|
|
|
|
if (stream != null) {
|
|
stream.close();
|
|
}
|
|
} catch (IOException var6) {
|
|
LOGGER.warn("Failed to list temporary dir {}", this.tempDataPackDir);
|
|
}
|
|
}
|
|
|
|
this.tempDataPackDir = null;
|
|
}
|
|
|
|
private static void copyBetweenDirs(Path fromDir, Path toDir, Path filePath) {
|
|
try {
|
|
Util.copyBetweenDirs(fromDir, toDir, filePath);
|
|
} catch (IOException var4) {
|
|
LOGGER.warn("Failed to copy datapack file from {} to {}", filePath, toDir);
|
|
throw new UncheckedIOException(var4);
|
|
}
|
|
}
|
|
|
|
private static Optional<LevelStorageSource.LevelStorageAccess> createNewWorldDirectory(Minecraft minecraft, String saveName, @Nullable Path tempDataPackDir) {
|
|
try {
|
|
LevelStorageSource.LevelStorageAccess levelStorageAccess = minecraft.getLevelSource().createAccess(saveName);
|
|
if (tempDataPackDir == null) {
|
|
return Optional.of(levelStorageAccess);
|
|
}
|
|
|
|
try {
|
|
Stream<Path> stream = Files.walk(tempDataPackDir);
|
|
|
|
Optional var6;
|
|
try {
|
|
Path path = levelStorageAccess.getLevelPath(LevelResource.DATAPACK_DIR);
|
|
FileUtil.createDirectoriesSafe(path);
|
|
stream.filter(path2 -> !path2.equals(tempDataPackDir)).forEach(path3 -> copyBetweenDirs(tempDataPackDir, path, path3));
|
|
var6 = Optional.of(levelStorageAccess);
|
|
} catch (Throwable var8) {
|
|
if (stream != null) {
|
|
try {
|
|
stream.close();
|
|
} catch (Throwable var7) {
|
|
var8.addSuppressed(var7);
|
|
}
|
|
}
|
|
|
|
throw var8;
|
|
}
|
|
|
|
if (stream != null) {
|
|
stream.close();
|
|
}
|
|
|
|
return var6;
|
|
} catch (UncheckedIOException | IOException var9) {
|
|
LOGGER.warn("Failed to copy datapacks to world {}", saveName, var9);
|
|
levelStorageAccess.close();
|
|
}
|
|
} catch (UncheckedIOException | IOException var10) {
|
|
LOGGER.warn("Failed to create access for {}", saveName, var10);
|
|
}
|
|
|
|
return Optional.empty();
|
|
}
|
|
|
|
@Nullable
|
|
public static Path createTempDataPackDirFromExistingWorld(Path datapackDir, Minecraft minecraft) {
|
|
MutableObject<Path> mutableObject = new MutableObject<>();
|
|
|
|
try {
|
|
Stream<Path> stream = Files.walk(datapackDir);
|
|
|
|
try {
|
|
stream.filter(path2 -> !path2.equals(datapackDir)).forEach(path2 -> {
|
|
Path path3 = mutableObject.getValue();
|
|
if (path3 == null) {
|
|
try {
|
|
path3 = Files.createTempDirectory("mcworld-");
|
|
} catch (IOException var5) {
|
|
LOGGER.warn("Failed to create temporary dir");
|
|
throw new UncheckedIOException(var5);
|
|
}
|
|
|
|
mutableObject.setValue(path3);
|
|
}
|
|
|
|
copyBetweenDirs(datapackDir, path3, path2);
|
|
});
|
|
} catch (Throwable var7) {
|
|
if (stream != null) {
|
|
try {
|
|
stream.close();
|
|
} catch (Throwable var6) {
|
|
var7.addSuppressed(var6);
|
|
}
|
|
}
|
|
|
|
throw var7;
|
|
}
|
|
|
|
if (stream != null) {
|
|
stream.close();
|
|
}
|
|
} catch (UncheckedIOException | IOException var8) {
|
|
LOGGER.warn("Failed to copy datapacks from world {}", datapackDir, var8);
|
|
SystemToast.onPackCopyFailure(minecraft, datapackDir.toString());
|
|
return null;
|
|
}
|
|
|
|
return mutableObject.getValue();
|
|
}
|
|
|
|
@Nullable
|
|
private Pair<Path, PackRepository> getDataPackSelectionSettings(WorldDataConfiguration worldDataConfiguration) {
|
|
Path path = this.getOrCreateTempDataPackDir();
|
|
if (path != null) {
|
|
if (this.tempDataPackRepository == null) {
|
|
this.tempDataPackRepository = ServerPacksSource.createPackRepository(path, this.packValidator);
|
|
this.tempDataPackRepository.reload();
|
|
}
|
|
|
|
this.tempDataPackRepository.setSelected(worldDataConfiguration.dataPacks().getEnabled());
|
|
return Pair.of(path, this.tempDataPackRepository);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
class GameTab extends GridLayoutTab {
|
|
private static final Component TITLE = Component.translatable("createWorld.tab.game.title");
|
|
private static final Component ALLOW_COMMANDS = Component.translatable("selectWorld.allowCommands");
|
|
private final EditBox nameEdit;
|
|
|
|
GameTab() {
|
|
super(TITLE);
|
|
RowHelper rowHelper = this.layout.rowSpacing(8).createRowHelper(1);
|
|
LayoutSettings layoutSettings = rowHelper.newCellSettings();
|
|
this.nameEdit = new EditBox(CreateWorldScreen.this.font, 208, 20, Component.translatable("selectWorld.enterName"));
|
|
this.nameEdit.setValue(CreateWorldScreen.this.uiState.getName());
|
|
this.nameEdit.setResponder(CreateWorldScreen.this.uiState::setName);
|
|
CreateWorldScreen.this.uiState
|
|
.addListener(
|
|
worldCreationUiState -> this.nameEdit
|
|
.setTooltip(
|
|
Tooltip.create(
|
|
Component.translatable("selectWorld.targetFolder", Component.literal(worldCreationUiState.getTargetFolder()).withStyle(ChatFormatting.ITALIC))
|
|
)
|
|
)
|
|
);
|
|
CreateWorldScreen.this.setInitialFocus(this.nameEdit);
|
|
rowHelper.addChild(
|
|
CommonLayouts.labeledElement(CreateWorldScreen.this.font, this.nameEdit, CreateWorldScreen.NAME_LABEL),
|
|
rowHelper.newCellSettings().alignHorizontallyCenter()
|
|
);
|
|
CycleButton<SelectedGameMode> cycleButton = rowHelper.addChild(
|
|
CycleButton.<SelectedGameMode>builder(selectedGameMode -> selectedGameMode.displayName)
|
|
.withValues(SelectedGameMode.SURVIVAL, SelectedGameMode.HARDCORE, SelectedGameMode.CREATIVE)
|
|
.create(
|
|
0, 0, 210, 20, CreateWorldScreen.GAME_MODEL_LABEL, (cycleButtonx, selectedGameMode) -> CreateWorldScreen.this.uiState.setGameMode(selectedGameMode)
|
|
),
|
|
layoutSettings
|
|
);
|
|
CreateWorldScreen.this.uiState.addListener(worldCreationUiState -> {
|
|
cycleButton.setValue(worldCreationUiState.getGameMode());
|
|
cycleButton.active = !worldCreationUiState.isDebug();
|
|
cycleButton.setTooltip(Tooltip.create(worldCreationUiState.getGameMode().getInfo()));
|
|
});
|
|
CycleButton<Difficulty> cycleButton2 = rowHelper.addChild(
|
|
CycleButton.<Difficulty>builder(Difficulty::getDisplayName)
|
|
.withValues(Difficulty.values())
|
|
.create(
|
|
0, 0, 210, 20, Component.translatable("options.difficulty"), (cycleButtonx, difficulty) -> CreateWorldScreen.this.uiState.setDifficulty(difficulty)
|
|
),
|
|
layoutSettings
|
|
);
|
|
CreateWorldScreen.this.uiState.addListener(worldCreationUiState -> {
|
|
cycleButton2.setValue(CreateWorldScreen.this.uiState.getDifficulty());
|
|
cycleButton2.active = !CreateWorldScreen.this.uiState.isHardcore();
|
|
cycleButton2.setTooltip(Tooltip.create(CreateWorldScreen.this.uiState.getDifficulty().getInfo()));
|
|
});
|
|
CycleButton<Boolean> cycleButton3 = rowHelper.addChild(
|
|
CycleButton.onOffBuilder()
|
|
.withTooltip(boolean_ -> Tooltip.create(CreateWorldScreen.ALLOW_COMMANDS_INFO))
|
|
.create(0, 0, 210, 20, ALLOW_COMMANDS, (cycleButtonx, boolean_) -> CreateWorldScreen.this.uiState.setAllowCommands(boolean_))
|
|
);
|
|
CreateWorldScreen.this.uiState.addListener(worldCreationUiState -> {
|
|
cycleButton3.setValue(CreateWorldScreen.this.uiState.isAllowCommands());
|
|
cycleButton3.active = !CreateWorldScreen.this.uiState.isDebug() && !CreateWorldScreen.this.uiState.isHardcore();
|
|
});
|
|
if (!SharedConstants.getCurrentVersion().isStable()) {
|
|
rowHelper.addChild(
|
|
Button.builder(
|
|
CreateWorldScreen.EXPERIMENTS_LABEL,
|
|
button -> CreateWorldScreen.this.openExperimentsScreen(CreateWorldScreen.this.uiState.getSettings().dataConfiguration())
|
|
)
|
|
.width(210)
|
|
.build()
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
class MoreTab extends GridLayoutTab {
|
|
private static final Component TITLE = Component.translatable("createWorld.tab.more.title");
|
|
private static final Component GAME_RULES_LABEL = Component.translatable("selectWorld.gameRules");
|
|
private static final Component DATA_PACKS_LABEL = Component.translatable("selectWorld.dataPacks");
|
|
|
|
MoreTab() {
|
|
super(TITLE);
|
|
RowHelper rowHelper = this.layout.rowSpacing(8).createRowHelper(1);
|
|
rowHelper.addChild(Button.builder(GAME_RULES_LABEL, button -> this.openGameRulesScreen()).width(210).build());
|
|
rowHelper.addChild(
|
|
Button.builder(
|
|
CreateWorldScreen.EXPERIMENTS_LABEL,
|
|
button -> CreateWorldScreen.this.openExperimentsScreen(CreateWorldScreen.this.uiState.getSettings().dataConfiguration())
|
|
)
|
|
.width(210)
|
|
.build()
|
|
);
|
|
rowHelper.addChild(
|
|
Button.builder(
|
|
DATA_PACKS_LABEL, button -> CreateWorldScreen.this.openDataPackSelectionScreen(CreateWorldScreen.this.uiState.getSettings().dataConfiguration())
|
|
)
|
|
.width(210)
|
|
.build()
|
|
);
|
|
}
|
|
|
|
private void openGameRulesScreen() {
|
|
CreateWorldScreen.this.minecraft
|
|
.setScreen(
|
|
new EditGameRulesScreen(
|
|
CreateWorldScreen.this.uiState.getGameRules().copy(CreateWorldScreen.this.uiState.getSettings().dataConfiguration().enabledFeatures()), optional -> {
|
|
CreateWorldScreen.this.minecraft.setScreen(CreateWorldScreen.this);
|
|
optional.ifPresent(CreateWorldScreen.this.uiState::setGameRules);
|
|
}
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
class WorldTab extends GridLayoutTab {
|
|
private static final Component TITLE = Component.translatable("createWorld.tab.world.title");
|
|
private static final Component AMPLIFIED_HELP_TEXT = Component.translatable("generator.minecraft.amplified.info");
|
|
private static final Component GENERATE_STRUCTURES = Component.translatable("selectWorld.mapFeatures");
|
|
private static final Component GENERATE_STRUCTURES_INFO = Component.translatable("selectWorld.mapFeatures.info");
|
|
private static final Component BONUS_CHEST = Component.translatable("selectWorld.bonusItems");
|
|
private static final Component SEED_LABEL = Component.translatable("selectWorld.enterSeed");
|
|
static final Component SEED_EMPTY_HINT = Component.translatable("selectWorld.seedInfo").withStyle(ChatFormatting.DARK_GRAY);
|
|
private static final int WORLD_TAB_WIDTH = 310;
|
|
private final EditBox seedEdit;
|
|
private final Button customizeTypeButton;
|
|
|
|
WorldTab() {
|
|
super(TITLE);
|
|
RowHelper rowHelper = this.layout.columnSpacing(10).rowSpacing(8).createRowHelper(2);
|
|
CycleButton<WorldTypeEntry> cycleButton = rowHelper.addChild(
|
|
CycleButton.<WorldTypeEntry>builder(WorldTypeEntry::describePreset)
|
|
.withValues(this.createWorldTypeValueSupplier())
|
|
.withCustomNarration(CreateWorldScreen.WorldTab::createTypeButtonNarration)
|
|
.create(
|
|
0,
|
|
0,
|
|
150,
|
|
20,
|
|
Component.translatable("selectWorld.mapType"),
|
|
(cycleButtonx, worldTypeEntry) -> CreateWorldScreen.this.uiState.setWorldType(worldTypeEntry)
|
|
)
|
|
);
|
|
cycleButton.setValue(CreateWorldScreen.this.uiState.getWorldType());
|
|
CreateWorldScreen.this.uiState.addListener(worldCreationUiState -> {
|
|
WorldTypeEntry worldTypeEntry = worldCreationUiState.getWorldType();
|
|
cycleButton.setValue(worldTypeEntry);
|
|
if (worldTypeEntry.isAmplified()) {
|
|
cycleButton.setTooltip(Tooltip.create(AMPLIFIED_HELP_TEXT));
|
|
} else {
|
|
cycleButton.setTooltip(null);
|
|
}
|
|
|
|
cycleButton.active = CreateWorldScreen.this.uiState.getWorldType().preset() != null;
|
|
});
|
|
this.customizeTypeButton = rowHelper.addChild(Button.builder(Component.translatable("selectWorld.customizeType"), button -> this.openPresetEditor()).build());
|
|
CreateWorldScreen.this.uiState
|
|
.addListener(worldCreationUiState -> this.customizeTypeButton.active = !worldCreationUiState.isDebug() && worldCreationUiState.getPresetEditor() != null);
|
|
this.seedEdit = new 1(this, CreateWorldScreen.this.font, 308, 20, Component.translatable("selectWorld.enterSeed"), CreateWorldScreen.this);
|
|
this.seedEdit.setHint(SEED_EMPTY_HINT);
|
|
this.seedEdit.setValue(CreateWorldScreen.this.uiState.getSeed());
|
|
this.seedEdit.setResponder(string -> CreateWorldScreen.this.uiState.setSeed(this.seedEdit.getValue()));
|
|
rowHelper.addChild(CommonLayouts.labeledElement(CreateWorldScreen.this.font, this.seedEdit, SEED_LABEL), 2);
|
|
Builder builder = SwitchGrid.builder(310);
|
|
builder.addSwitch(GENERATE_STRUCTURES, CreateWorldScreen.this.uiState::isGenerateStructures, CreateWorldScreen.this.uiState::setGenerateStructures)
|
|
.withIsActiveCondition(() -> !CreateWorldScreen.this.uiState.isDebug())
|
|
.withInfo(GENERATE_STRUCTURES_INFO);
|
|
builder.addSwitch(BONUS_CHEST, CreateWorldScreen.this.uiState::isBonusChest, CreateWorldScreen.this.uiState::setBonusChest)
|
|
.withIsActiveCondition(() -> !CreateWorldScreen.this.uiState.isHardcore() && !CreateWorldScreen.this.uiState.isDebug());
|
|
SwitchGrid switchGrid = builder.build();
|
|
rowHelper.addChild(switchGrid.layout(), 2);
|
|
CreateWorldScreen.this.uiState.addListener(worldCreationUiState -> switchGrid.refreshStates());
|
|
}
|
|
|
|
private void openPresetEditor() {
|
|
PresetEditor presetEditor = CreateWorldScreen.this.uiState.getPresetEditor();
|
|
if (presetEditor != null) {
|
|
CreateWorldScreen.this.minecraft.setScreen(presetEditor.createEditScreen(CreateWorldScreen.this, CreateWorldScreen.this.uiState.getSettings()));
|
|
}
|
|
}
|
|
|
|
private ValueListSupplier<WorldTypeEntry> createWorldTypeValueSupplier() {
|
|
return new 2(this);
|
|
}
|
|
|
|
private static MutableComponent createTypeButtonNarration(CycleButton<WorldTypeEntry> button) {
|
|
return button.getValue().isAmplified()
|
|
? CommonComponents.joinForNarration(button.createDefaultNarrationMessage(), AMPLIFIED_HELP_TEXT)
|
|
: button.createDefaultNarrationMessage();
|
|
}
|
|
}
|
|
}
|