Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

添加对整合包可选文件的初步支持 #1771

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@
import com.google.gson.JsonParseException;
import org.apache.commons.compress.archivers.zip.ZipFile;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.mod.MismatchedModpackTypeException;
import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.mod.ModpackProvider;
import org.jackhuang.hmcl.mod.ModpackUpdateTask;
import org.jackhuang.hmcl.mod.*;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.StringUtils;
Expand All @@ -34,6 +31,7 @@
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.Set;

public final class HMCLModpackProvider implements ModpackProvider {
public static final HMCLModpackProvider INSTANCE = new HMCLModpackProvider();
Expand All @@ -49,7 +47,7 @@ public Task<?> createCompletionTask(DefaultDependencyManager dependencyManager,
}

@Override
public Task<?> createUpdateTask(DefaultDependencyManager dependencyManager, String name, File zipFile, Modpack modpack) throws MismatchedModpackTypeException {
public Task<?> createUpdateTask(DefaultDependencyManager dependencyManager, String name, File zipFile, Modpack modpack, Set<? extends ModpackFile> selectedFiles) throws MismatchedModpackTypeException {
if (!(modpack.getManifest() instanceof HMCLModpackManifest))
throw new MismatchedModpackTypeException(getName(), modpack.getManifest().getProvider().getName());

Expand Down Expand Up @@ -81,7 +79,7 @@ public Modpack readManifest(ZipFile file, Path path, Charset encoding) throws IO

private static class HMCLModpack extends Modpack {
@Override
public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, File zipFile, String name) {
public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, File zipFile, String name, Set<? extends ModpackFile> selectedFiles) {
return new HMCLModpackInstallTask(((HMCLGameRepository) dependencyManager.getGameRepository()).getProfile(), zipFile, this, name);
}
}
Expand Down
17 changes: 7 additions & 10 deletions HMCL/src/main/java/org/jackhuang/hmcl/game/ModpackHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.*;
import java.util.stream.Stream;

import static org.jackhuang.hmcl.util.Lang.mapOf;
Expand Down Expand Up @@ -190,7 +187,7 @@ public static Task<?> getInstallManuallyCreatedModpackTask(Profile profile, File
});
}

public static Task<?> getInstallTask(Profile profile, File zipFile, String name, Modpack modpack) {
public static Task<?> getInstallTask(Profile profile, File zipFile, String name, Modpack modpack, Set<? extends ModpackFile> selectedFiles) {
profile.getRepository().markVersionAsModpack(name);

ExceptionalRunnable<?> success = () -> {
Expand All @@ -210,15 +207,15 @@ public static Task<?> getInstallTask(Profile profile, File zipFile, String name,
};

if (modpack.getManifest() instanceof MultiMCInstanceConfiguration)
return modpack.getInstallTask(profile.getDependency(), zipFile, name)
return modpack.getInstallTask(profile.getDependency(), zipFile, name, selectedFiles)
.whenComplete(Schedulers.defaultScheduler(), success, failure)
.thenComposeAsync(createMultiMCPostInstallTask(profile, (MultiMCInstanceConfiguration) modpack.getManifest(), name));
else if (modpack.getManifest() instanceof McbbsModpackManifest)
return modpack.getInstallTask(profile.getDependency(), zipFile, name)
return modpack.getInstallTask(profile.getDependency(), zipFile, name, selectedFiles)
.whenComplete(Schedulers.defaultScheduler(), success, failure)
.thenComposeAsync(createMcbbsPostInstallTask(profile, (McbbsModpackManifest) modpack.getManifest(), name));
else
return modpack.getInstallTask(profile.getDependency(), zipFile, name)
return modpack.getInstallTask(profile.getDependency(), zipFile, name, selectedFiles)
.whenComplete(Schedulers.javafx(), success, failure);
}

Expand All @@ -232,13 +229,13 @@ public static Task<Void> getUpdateTask(Profile profile, ServerModpackManifest ma
}
}

public static Task<?> getUpdateTask(Profile profile, File zipFile, Charset charset, String name, ModpackConfiguration<?> configuration) throws UnsupportedModpackException, ManuallyCreatedModpackException, MismatchedModpackTypeException {
public static Task<?> getUpdateTask(Profile profile, File zipFile, Charset charset, String name, ModpackConfiguration<?> configuration, Set<? extends ModpackFile> selectedFiles) throws UnsupportedModpackException, ManuallyCreatedModpackException, MismatchedModpackTypeException {
Modpack modpack = ModpackHelper.readModpackManifest(zipFile.toPath(), charset);
ModpackProvider provider = getProviderByType(configuration.getType());
if (provider == null) {
throw new UnsupportedModpackException();
}
return provider.createUpdateTask(profile.getDependency(), name, zipFile, modpack);
return provider.createUpdateTask(profile.getDependency(), name, zipFile, modpack, selectedFiles);
}

public static void toVersionSetting(MultiMCInstanceConfiguration c, VersionSetting vs) {
Expand Down
CaveNightingale marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.construct;

import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXCheckBox;
import com.jfoenix.controls.JFXDialogLayout;
import com.jfoenix.controls.JFXListView;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.image.ImageView;
import javafx.scene.layout.*;
import org.jackhuang.hmcl.mod.ModpackFile;
import org.jackhuang.hmcl.mod.RemoteMod;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.util.Holder;

import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;

/**
* This page is used to ask player which optional file they want to install
* Support CurseForge modpack yet
*/
public class OptionalFilesSelectionPane extends BorderPane {
Set<ModpackFile> selected = new HashSet<>();

private final VBox title = new VBox();
private final Label retryOptionalFiles = new Label(i18n("modpack.retry_optional_files"));
private final Label pendingOptionalFiles = new Label(i18n("modpack.pending_optional_files"));
private final Label noOptionalFiles = new Label(i18n("modpack.no_optional_files"));
private Runnable retry;

private final JFXListView<ModpackFile> list = new JFXListView<>();

public OptionalFilesSelectionPane() {
retryOptionalFiles.setOnMouseClicked(e -> {
title.getChildren().remove(retryOptionalFiles);
retry.run();
});

Label label = new Label(i18n("modpack.optional_files"));
label.getStyleClass().add("subtitle");
title.getChildren().add(label);
this.setTop(title);
setPending();
}

public void setOptionalFileList(List<? extends ModpackFile> files) {
list.getItems().clear();
list.setCellFactory(it -> new OptionalFileEntry(list, new Holder<>()));
int i = 0;
for (ModpackFile file : files) {
selected.add(file);
if (file.isOptional()) {
list.getItems().add(file);
i++;
}
}
if (i != 0) {
this.setCenter(list);
} else {
this.setCenter(noOptionalFiles);
}
}

public void setPending() {
this.setCenter(pendingOptionalFiles);
retry = null;
}

public void setRetry(Runnable retry) {
title.getChildren().add(retryOptionalFiles);
this.retry = retry;
}

public Set<ModpackFile> getSelected() {
return selected;
}

private class OptionalFileEntry extends MDListCell<ModpackFile> {
JFXCheckBox checkBox = new JFXCheckBox();
TwoLineListItem content = new TwoLineListItem();
JFXButton infoButton = new JFXButton();
HBox container = new HBox(8);

public OptionalFileEntry(JFXListView<ModpackFile> listView, Holder<Object> lastCell) {
super(listView, lastCell);
container.setPickOnBounds(false);
container.setAlignment(Pos.CENTER_LEFT);
HBox.setHgrow(content, Priority.ALWAYS);
content.setMouseTransparent(true);
setSelectable();

infoButton.getStyleClass().add("toggle-icon4");
infoButton.setGraphic(FXUtils.limitingSize(SVG.INFORMATION_OUTLINE.createIcon(Theme.blackFill(), 24, 24), 24, 24));
getContainer().getChildren().setAll(container);
}

@Override
protected void updateControl(ModpackFile item, boolean empty) {
container.getChildren().setAll(checkBox, content);
if (empty) {
setGraphic(null);
} else {
String name = item.getFileName();
if (name != null) {
content.setTitle(name);
} else {
content.setTitle(i18n("modpack.unknown_optional_file"));
}
Optional<RemoteMod> mod = item.getMod();
RemoteMod mod1 = mod == null ? null : mod.orElse(null);
if (mod1 != null) {
content.setSubtitle(mod1.getTitle());
infoButton.setOnMouseClicked(e -> Controllers.dialog(new ModInfo(mod1)));
container.getChildren().add(infoButton);
} else {
content.setSubtitle("");
}
checkBox.setSelected(true);
checkBox.selectedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) {
selected.add(item);
} else {
selected.remove(item);
}
});
}
}
}

private static class ModInfo extends JFXDialogLayout {
public ModInfo(RemoteMod mod) {
HBox container = new HBox(8);
ImageView imageView = new ImageView(mod.getIconUrl());
imageView.setFitHeight(32);
imageView.setFitWidth(32);
container.getChildren().add(imageView);

TwoLineListItem title = new TwoLineListItem();
title.setTitle(mod.getTitle());
title.setSubtitle(mod.getAuthor());
container.getChildren().add(title);
setHeading(container);

Label description = new Label(mod.getDescription());
setBody(description);

JFXHyperlink pageButton = new JFXHyperlink();
pageButton.setText(i18n("mods.url"));
pageButton.setOnAction(e -> FXUtils.openLink(mod.getPageUrl()));
getActions().add(pageButton);

JFXButton okButton = new JFXButton();
okButton.getStyleClass().add("dialog-accept");
okButton.setText(i18n("button.ok"));
okButton.setOnAction(e -> fireEvent(new DialogCloseEvent()));
getActions().add(okButton);

onEscPressed(this, okButton::fire);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import org.jackhuang.hmcl.game.ManuallyCreatedModpackException;
import org.jackhuang.hmcl.game.ModpackHelper;
import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.mod.ModpackFile;
import org.jackhuang.hmcl.mod.ModpackManifest;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.Profiles;
import org.jackhuang.hmcl.task.Schedulers;
Expand All @@ -40,6 +42,8 @@

import java.io.File;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.logging.Level;
Expand Down Expand Up @@ -101,9 +105,9 @@ public LocalModpackPage(WizardController controller) {
.thenApplyAsync(encoding -> {
charset = encoding;
manifest = ModpackHelper.readModpackManifest(selectedFile.toPath(), encoding);
pendingOptionalFiles(manifest);
return manifest;
})
.whenComplete(Schedulers.javafx(), (manifest, exception) -> {
}).whenComplete(Schedulers.javafx(), (_unused, exception) -> {
if (exception instanceof ManuallyCreatedModpackException) {
hideSpinner();
lblName.setText(selectedFile.getName());
Expand Down Expand Up @@ -141,6 +145,25 @@ public LocalModpackPage(WizardController controller) {
}).start();
}

private void pendingOptionalFiles(Modpack manifest) {
waitingForOptionalFiles.set(true);
if (!(manifest.getManifest() instanceof ModpackManifest.SupportOptional)) {
optionalFiles.setOptionalFileList(Collections.emptyList());
return;
}
optionalFiles.setPending();
Task.supplyAsync(() -> manifest.setManifest(manifest.getManifest().getProvider().loadFiles(manifest.getManifest())))
.whenComplete(Schedulers.javafx(), (manifest1, exception) -> {
List<? extends ModpackFile> files = ((ModpackManifest.SupportOptional) manifest1.getManifest()).getFiles();
optionalFiles.setOptionalFileList(files);
waitingForOptionalFiles.set(false);
if (exception != null || files.stream().anyMatch(s -> s.isOptional() && (s.getMod() == null || s.getFileName() == null))) {
LOG.log(Level.WARNING, "Failed to load optional files", exception);
optionalFiles.setRetry(() -> pendingOptionalFiles(manifest));
}
}).start();
}

@Override
public void cleanup(Map<String, Object> settings) {
settings.remove(MODPACK_FILE);
Expand All @@ -150,6 +173,7 @@ protected void onInstall() {
if (!txtModpackName.validate()) return;
controller.getSettings().put(MODPACK_NAME, txtModpackName.getText());
controller.getSettings().put(MODPACK_CHARSET, charset);
controller.getSettings().put(MODPACK_SELECTED_FILES, optionalFiles.getSelected());
controller.onFinish();
}

Expand All @@ -164,4 +188,6 @@ protected void onDescribe() {
public static final String MODPACK_MANIFEST = "MODPACK_MANIFEST";
public static final String MODPACK_CHARSET = "MODPACK_CHARSET";
public static final String MODPACK_MANUALLY_CREATED = "MODPACK_MANUALLY_CREATED";

public static final String MODPACK_SELECTED_FILES = "MODPACK_SELECTED_FILES";
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@
import javafx.scene.Node;
import org.jackhuang.hmcl.game.ModpackHelper;
import org.jackhuang.hmcl.game.ManuallyCreatedModpackException;
import org.jackhuang.hmcl.mod.ModpackCompletionException;
import org.jackhuang.hmcl.mod.MismatchedModpackTypeException;
import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.mod.UnsupportedModpackException;
import org.jackhuang.hmcl.mod.*;
import org.jackhuang.hmcl.mod.server.ServerModpackManifest;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.task.Schedulers;
Expand All @@ -38,6 +35,7 @@
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.Set;

import static org.jackhuang.hmcl.util.Lang.tryCast;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
Expand Down Expand Up @@ -74,6 +72,7 @@ public void start(Map<String, Object> settings) {
settings.put(PROFILE, profile);
}

@SuppressWarnings("unchecked")
private Task<?> finishModpackInstallingAsync(Map<String, Object> settings) {
File selected = tryCast(settings.get(LocalModpackPage.MODPACK_FILE), File.class).orElse(null);
ServerModpackManifest serverModpackManifest = tryCast(settings.get(RemoteModpackPage.MODPACK_SERVER_MANIFEST), ServerModpackManifest.class).orElse(null);
Expand All @@ -97,7 +96,7 @@ private Task<?> finishModpackInstallingAsync(Map<String, Object> settings) {
if (serverModpackManifest != null) {
return ModpackHelper.getUpdateTask(profile, serverModpackManifest, modpack.getEncoding(), name, ModpackHelper.readModpackConfiguration(profile.getRepository().getModpackConfiguration(name)));
} else {
return ModpackHelper.getUpdateTask(profile, selected, modpack.getEncoding(), name, ModpackHelper.readModpackConfiguration(profile.getRepository().getModpackConfiguration(name)));
return ModpackHelper.getUpdateTask(profile, selected, modpack.getEncoding(), name, ModpackHelper.readModpackConfiguration(profile.getRepository().getModpackConfiguration(name)), (Set<? extends ModpackFile>) settings.get(LocalModpackPage.MODPACK_SELECTED_FILES));
}
} catch (UnsupportedModpackException | ManuallyCreatedModpackException e) {
Controllers.dialog(i18n("modpack.unsupported"), i18n("message.error"), MessageType.ERROR);
Expand All @@ -112,7 +111,7 @@ private Task<?> finishModpackInstallingAsync(Map<String, Object> settings) {
return ModpackHelper.getInstallTask(profile, serverModpackManifest, name, modpack)
.thenRunAsync(Schedulers.javafx(), () -> profile.setSelectedVersion(name));
} else {
return ModpackHelper.getInstallTask(profile, selected, name, modpack)
return ModpackHelper.getInstallTask(profile, selected, name, modpack, (Set<? extends ModpackFile>) settings.get(LocalModpackPage.MODPACK_SELECTED_FILES))
.thenRunAsync(Schedulers.javafx(), () -> profile.setSelectedVersion(name));
}
}
Expand Down