make launch script

This commit is contained in:
huangyuhui
2018-01-24 19:42:04 +08:00
parent 6db41431ed
commit 056c0901f2
34 changed files with 610 additions and 258 deletions

View File

@@ -39,11 +39,11 @@ import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.DialogController;
import org.jackhuang.hmcl.ui.LaunchingStepsPane;
import org.jackhuang.hmcl.ui.LogWindow;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.Log4jLevel;
import org.jackhuang.hmcl.util.ManagedProcess;
import org.jackhuang.hmcl.util.Pair;
import org.jackhuang.hmcl.ui.construct.MessageBox;
import org.jackhuang.hmcl.util.*;
import java.io.*;
import java.nio.Buffer;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
@@ -55,7 +55,7 @@ public final class LauncherHelper {
private final LaunchingStepsPane launchingStepsPane = new LaunchingStepsPane();
public static final Queue<ManagedProcess> PROCESSES = new ConcurrentLinkedQueue<>();
public void launch(String selectedVersion) {
public void launch(String selectedVersion, String launcherName) {
Profile profile = Settings.INSTANCE.getSelectedProfile();
GameRepository repository = profile.getRepository();
DefaultDependencyManager dependencyManager = profile.getDependency();
@@ -86,16 +86,26 @@ public final class LauncherHelper {
repository, selectedVersion, variables.get("account"), setting.toLaunchOptions(profile.getGameDir()), new HMCLProcessListener(variables.get("account"), setting)
));
}))
.then(variables -> variables.<DefaultLauncher>get("launcher").launchAsync())
.then(variables -> {
if (launcherName == null) {
return variables.<DefaultLauncher>get("launcher").launchAsync();
} else {
variables.set("script", variables.<DefaultLauncher>get("launcher").makeLaunchScript(launcherName));
return null;
}
})
.then(Task.of(variables -> {
PROCESSES.add(variables.get(DefaultLauncher.LAUNCH_ASYNC_ID));
if (setting.getLauncherVisibility() == LauncherVisibility.CLOSE)
Main.stopApplication();
if (launcherName == null) {
PROCESSES.add(variables.get(DefaultLauncher.LAUNCH_ASYNC_ID));
if (setting.getLauncherVisibility() == LauncherVisibility.CLOSE)
Main.stopApplication();
}
}))
.executor();
executor.setTaskListener(new TaskListener() {
executor.addTaskListener(new TaskListener() {
AtomicInteger finished = new AtomicInteger(0);
@Override
public void onFinished(Task task) {
finished.incrementAndGet();
@@ -105,14 +115,43 @@ public final class LauncherHelper {
}
@Override
public void onTerminate() {
Platform.runLater(Controllers::closeDialog);
public void onTerminate(AutoTypingMap<String> variables) {
Platform.runLater(() -> {
Controllers.closeDialog();
if (variables.containsKey("script")) {
Controllers.dialog(Main.i18n("version.launch_script.success", variables.<File>get("script").getAbsolutePath()));
}
});
}
});
executor.start();
}
private void checkGameState(VersionSetting setting) throws InterruptedException {
JavaVersion java = setting.getJavaVersion();
if (java == null) {
// TODO
return;
}
if (java.getParsedVersion() < JavaVersion.JAVA_8) {
MessageBox.show(Main.i18n("launch.advice.newer_java"));
}
if (java.getPlatform() == org.jackhuang.hmcl.util.Platform.BIT_32 &&
org.jackhuang.hmcl.util.Platform.IS_64_BIT) {
MessageBox.show(Main.i18n("launch.advice.different_platform"));
}
if (java.getPlatform() == org.jackhuang.hmcl.util.Platform.BIT_32 &&
setting.getMaxMemory() > 1.5 * 1024) {
MessageBox.show(Main.i18n("launch.advice.too_large_memory_for_32bit"));
}
if (OperatingSystem.TOTAL_MEMORY > 0 && OperatingSystem.TOTAL_MEMORY < setting.getMaxMemory()) {
MessageBox.show(Main.i18n("launch.advice.not_enough_space"));
}
}
public static void stopManagedProcesses() {
synchronized (PROCESSES) {
while (!PROCESSES.isEmpty())

View File

@@ -46,6 +46,12 @@ public final class VersionSetting {
this.global = global;
}
private final ImmediateBooleanProperty usesGlobalProperty = new ImmediateBooleanProperty(this, "usesGlobal", false);
public ImmediateBooleanProperty usesGlobalProperty() {
return usesGlobalProperty;
}
/**
* HMCL Version Settings have been divided into 2 parts.
* 1. Global settings.
@@ -54,12 +60,6 @@ public final class VersionSetting {
*
* Defaults false because if one version uses global first, custom version file will not be generated.
*/
private final ImmediateBooleanProperty usesGlobalProperty = new ImmediateBooleanProperty(this, "usesGlobal", false);
public ImmediateBooleanProperty usesGlobalProperty() {
return usesGlobalProperty;
}
public boolean isUsesGlobal() {
return usesGlobalProperty.get();
}
@@ -70,15 +70,15 @@ public final class VersionSetting {
// java
/**
* Java version or null if user customizes java directory.
*/
private final ImmediateStringProperty javaProperty = new ImmediateStringProperty(this, "java", "");
public ImmediateStringProperty javaProperty() {
return javaProperty;
}
/**
* Java version or null if user customizes java directory.
*/
public String getJava() {
return javaProperty.get();
}
@@ -87,15 +87,15 @@ public final class VersionSetting {
javaProperty.set(java);
}
/**
* User customized java directory or null if user uses system Java.
*/
private final ImmediateStringProperty javaDirProperty = new ImmediateStringProperty(this, "javaDir", "");
public ImmediateStringProperty javaDirProperty() {
return javaDirProperty;
}
/**
* User customized java directory or null if user uses system Java.
*/
public String getJavaDir() {
return javaDirProperty.get();
}
@@ -104,15 +104,15 @@ public final class VersionSetting {
javaDirProperty.set(javaDir);
}
/**
* The command to launch java, i.e. optirun.
*/
private final ImmediateStringProperty wrapperProperty = new ImmediateStringProperty(this, "wrapper", "");
public ImmediateStringProperty wrapperProperty() {
return wrapperProperty;
}
/**
* The command to launch java, i.e. optirun.
*/
public String getWrapper() {
return wrapperProperty.get();
}
@@ -121,15 +121,15 @@ public final class VersionSetting {
wrapperProperty.set(wrapper);
}
/**
* The permanent generation size of JVM garbage collection.
*/
private final ImmediateStringProperty permSizeProperty = new ImmediateStringProperty(this, "permSize", "");
public ImmediateStringProperty permSizeProperty() {
return permSizeProperty;
}
/**
* The permanent generation size of JVM garbage collection.
*/
public String getPermSize() {
return permSizeProperty.get();
}
@@ -138,15 +138,15 @@ public final class VersionSetting {
permSizeProperty.set(permSize);
}
/**
* The maximum memory that JVM can allocate for heap.
*/
private final ImmediateIntegerProperty maxMemoryProperty = new ImmediateIntegerProperty(this, "maxMemory", (int) OperatingSystem.SUGGESTED_MEMORY);
public ImmediateIntegerProperty maxMemoryProperty() {
return maxMemoryProperty;
}
/**
* The maximum memory/MB that JVM can allocate for heap.
*/
public int getMaxMemory() {
return maxMemoryProperty.get();
}
@@ -172,16 +172,16 @@ public final class VersionSetting {
minMemoryProperty.set(minMemory);
}
/**
* The command that will be executed before launching the Minecraft.
* Operating system relevant.
*/
private final ImmediateStringProperty preLaunchCommandProperty = new ImmediateStringProperty(this, "precalledCommand", "");
public ImmediateStringProperty preLaunchCommandProperty() {
return preLaunchCommandProperty;
}
/**
* The command that will be executed before launching the Minecraft.
* Operating system relevant.
*/
public String getPreLaunchCommand() {
return preLaunchCommandProperty.get();
}
@@ -192,15 +192,15 @@ public final class VersionSetting {
// options
/**
* The user customized arguments passed to JVM.
*/
private final ImmediateStringProperty javaArgsProperty = new ImmediateStringProperty(this, "javaArgs", "");
public ImmediateStringProperty javaArgsProperty() {
return javaArgsProperty;
}
/**
* The user customized arguments passed to JVM.
*/
public String getJavaArgs() {
return javaArgsProperty.get();
}
@@ -209,16 +209,15 @@ public final class VersionSetting {
javaArgsProperty.set(javaArgs);
}
/**
* The user customized arguments passed to Minecraft.
*/
private final ImmediateStringProperty minecraftArgsProperty = new ImmediateStringProperty(this, "minecraftArgs", "");
public ImmediateStringProperty minecraftArgsProperty() {
return minecraftArgsProperty;
}
/**
* The user customized arguments passed to Minecraft.
*/
public String getMinecraftArgs() {
return minecraftArgsProperty.get();
}
@@ -227,15 +226,15 @@ public final class VersionSetting {
minecraftArgsProperty.set(minecraftArgs);
}
/**
* True if disallow HMCL use default JVM arguments.
*/
private final ImmediateBooleanProperty noJVMArgsProperty = new ImmediateBooleanProperty(this, "noJVMArgs", false);
public ImmediateBooleanProperty noJVMArgsProperty() {
return noJVMArgsProperty;
}
/**
* True if disallow HMCL use default JVM arguments.
*/
public boolean isNoJVMArgs() {
return noJVMArgsProperty.get();
}
@@ -244,15 +243,15 @@ public final class VersionSetting {
noJVMArgsProperty.set(noJVMArgs);
}
/**
* True if HMCL does not check game's completeness.
*/
private final ImmediateBooleanProperty notCheckGameProperty = new ImmediateBooleanProperty(this, "notCheckGame", false);
public ImmediateBooleanProperty notCheckGameProperty() {
return notCheckGameProperty;
}
/**
* True if HMCL does not check game's completeness.
*/
public boolean isNotCheckGame() {
return notCheckGameProperty.get();
}
@@ -261,16 +260,15 @@ public final class VersionSetting {
notCheckGameProperty.set(notCheckGame);
}
/**
* True if HMCL does not find/download libraries in/to common path.
*/
private final ImmediateBooleanProperty noCommonProperty = new ImmediateBooleanProperty(this, "noCommon", false);
public ImmediateBooleanProperty noCommonProperty() {
return noCommonProperty;
}
/**
* True if HMCL does not find/download libraries in/to common path.
*/
public boolean isNoCommon() {
return noCommonProperty.get();
}
@@ -279,15 +277,15 @@ public final class VersionSetting {
noCommonProperty.set(noCommon);
}
/**
* True if show the logs after game launched.
*/
private final ImmediateBooleanProperty showLogsProperty = new ImmediateBooleanProperty(this, "showLogs", false);
public ImmediateBooleanProperty showLogsProperty() {
return showLogsProperty;
}
/**
* True if show the logs after game launched.
*/
public boolean isShowLogs() {
return showLogsProperty.get();
}
@@ -298,17 +296,17 @@ public final class VersionSetting {
// Minecraft settings.
/**
* The server ip that will be entered after Minecraft successfully loaded immediately.
*
* Format: ip:port or without port.
*/
private final ImmediateStringProperty serverIpProperty = new ImmediateStringProperty(this, "serverIp", "");
public ImmediateStringProperty serverIpProperty() {
return serverIpProperty;
}
/**
* The server ip that will be entered after Minecraft successfully loaded immediately.
*
* Format: ip:port or without port.
*/
public String getServerIp() {
return serverIpProperty.get();
}
@@ -318,15 +316,15 @@ public final class VersionSetting {
}
/**
* True if Minecraft started in fullscreen mode.
*/
private final ImmediateBooleanProperty fullscreenProperty = new ImmediateBooleanProperty(this, "fullscreen", false);
public ImmediateBooleanProperty fullscreenProperty() {
return fullscreenProperty;
}
/**
* True if Minecraft started in fullscreen mode.
*/
public boolean isFullscreen() {
return fullscreenProperty.get();
}
@@ -335,19 +333,19 @@ public final class VersionSetting {
fullscreenProperty.set(fullscreen);
}
/**
* The width of Minecraft window, defaults 800.
*
* The field saves int value.
* String type prevents unexpected value from causing JsonSyntaxException.
* We can only reset this field instead of recreating the whole setting file.
*/
private final ImmediateIntegerProperty widthProperty = new ImmediateIntegerProperty(this, "width", 854);
public ImmediateIntegerProperty widthProperty() {
return widthProperty;
}
/**
* The width of Minecraft window, defaults 800.
*
* The field saves int value.
* String type prevents unexpected value from JsonParseException.
* We can only reset this field instead of recreating the whole setting file.
*/
public int getWidth() {
return widthProperty.get();
}
@@ -357,19 +355,19 @@ public final class VersionSetting {
}
/**
* The height of Minecraft window, defaults 480.
*
* The field saves int value.
* String type prevents unexpected value from causing JsonSyntaxException.
* We can only reset this field instead of recreating the whole setting file.
*/
private final ImmediateIntegerProperty heightProperty = new ImmediateIntegerProperty(this, "height", 480);
public ImmediateIntegerProperty heightProperty() {
return heightProperty;
}
/**
* The height of Minecraft window, defaults 480.
*
* The field saves int value.
* String type prevents unexpected value from JsonParseException.
* We can only reset this field instead of recreating the whole setting file.
*/
public int getHeight() {
return heightProperty.get();
}

View File

@@ -17,6 +17,7 @@
*/
package org.jackhuang.hmcl.ui;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Pane;
@@ -51,7 +52,29 @@ public class AdvancedListBox extends ScrollPane {
return this;
}
public AdvancedListBox remove(Node child) {
if (child instanceof Pane)
container.getChildren().remove(child);
else {
StackPane pane = null;
for (Node node : container.getChildren())
if (node instanceof StackPane) {
ObservableList<Node> list = ((StackPane) node).getChildren();
if (list.size() == 1 && list.get(0) == child)
pane = (StackPane) node;
}
if (pane == null)
throw new Error();
container.getChildren().remove(pane);
}
return this;
}
public AdvancedListBox startCategory(String category) {
return add(new ClassTitle(category));
}
public void setSpacing(double spacing) {
container.setSpacing(spacing);
}
}

View File

@@ -26,8 +26,12 @@ import javafx.stage.Stage;
import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.construct.InputDialogPane;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
import org.jackhuang.hmcl.util.JavaVersion;
import java.util.function.Consumer;
public final class Controllers {
private static Scene scene;
@@ -103,6 +107,10 @@ public final class Controllers {
dialog(new MessageDialogPane(text, decorator.getDialog()));
}
public static void inputDialog(String text, Consumer<String> onResult) {
dialog(new InputDialogPane(text, decorator.getDialog(), onResult));
}
public static void closeDialog() {
decorator.getDialog().close();
}

View File

@@ -76,11 +76,16 @@ public final class MainPage extends StackPane implements DecoratorPage {
if (Settings.INSTANCE.getSelectedAccount() == null)
Controllers.dialog(Main.i18n("login.no_Player007"));
else
LauncherHelper.INSTANCE.launch(version);
LauncherHelper.INSTANCE.launch(version, null);
});
item.setOnDeleteButtonClicked(e -> {
profile.getRepository().removeVersionFromDisk(version);
Platform.runLater(this::loadVersions);
item.setOnScriptButtonClicked(e -> {
if (Settings.INSTANCE.getSelectedAccount() == null)
Controllers.dialog(Main.i18n("login.no_Player007"));
else {
Controllers.inputDialog(Main.i18n("mainwindow.enter_script_name"), file -> {
LauncherHelper.INSTANCE.launch(version, file);
});
}
});
item.setOnSettingsButtonClicked(e -> {
Controllers.getDecorator().showPage(Controllers.getVersionPage());

View File

@@ -81,6 +81,10 @@ public final class SVG {
return createSVGPath("M1008 6.286q18.857 13.714 15.429 36.571l-146.286 877.714q-2.857 16.571-18.286 25.714-8 4.571-17.714 4.571-6.286 0-13.714-2.857l-258.857-105.714-138.286 168.571q-10.286 13.143-28 13.143-7.429 0-12.571-2.286-10.857-4-17.429-13.429t-6.571-20.857v-199.429l493.714-605.143-610.857 528.571-225.714-92.571q-21.143-8-22.857-31.429-1.143-22.857 18.286-33.714l950.857-548.571q8.571-5.143 18.286-5.14311.429 0 20.571 6.286z", fill, width, height);
}
public static Node script(String fill, double width, double height) {
return createSVGPath("M14,20A2,2 0 0,0 16,18V5H9A1,1 0 0,0 8,6V16H5V5A3,3 0 0,1 8,2H19A3,3 0 0,1 22,5V6H18V18L18,19A3,3 0 0,1 15,22H5A3,3 0 0,1 2,19V18H12A2,2 0 0,0 14,20Z", fill, width, height);
}
public static Node pencil(String fill, double width, double height) {
return createSVGPath("M20.71,4.04C21.1,3.65 21.1,3 20.71,2.63L18.37,0.29C18,-0.1 17.35,-0.1 16.96,0.29L15,2.25L18.75,6M17.75,7L14,3.25L4,13.25V17H7.75L17.75,7Z", fill, width, height);
}

View File

@@ -26,12 +26,15 @@ import javafx.scene.effect.BlurType;
import javafx.scene.effect.DropShadow;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import java.util.Optional;
public final class VersionItem extends StackPane {
@FXML
private Pane icon;
@@ -42,29 +45,37 @@ public final class VersionItem extends StackPane {
@FXML
private StackPane body;
@FXML
private JFXButton btnDelete;
@FXML
private JFXButton btnSettings;
@FXML
private JFXButton btnLaunch;
@FXML
private JFXButton btnScript;
@FXML
private Label lblVersionName;
@FXML
private Label lblGameVersion;
@FXML
private ImageView iconView;
private EventHandler<? super MouseEvent> launchClickedHandler = null;
public VersionItem() {
FXUtils.loadFXML(this, "/assets/fxml/version-item.fxml");
FXUtils.limitWidth(this, 160);
FXUtils.limitHeight(this, 156);
setEffect(new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.26), 5.0, 0.12, -1.0, 1.0));
btnSettings.setGraphic(SVG.gear("black", 15, 15));
btnDelete.setGraphic(SVG.delete("black", 15, 15));
btnLaunch.setGraphic(SVG.launch("black", 15, 15));
btnScript.setGraphic(SVG.script("black", 15, 15));
icon.translateYProperty().bind(Bindings.createDoubleBinding(() -> header.getBoundsInParent().getHeight() - icon.getHeight() / 2 - 16, header.boundsInParentProperty(), icon.heightProperty()));
FXUtils.limitSize(iconView, 32, 32);
setOnMouseClicked(e -> {
if (e.getButton() == MouseButton.PRIMARY)
if (e.getClickCount() == 2)
Optional.ofNullable(launchClickedHandler).ifPresent(h -> h.handle(e));
});
}
public void setVersionName(String versionName) {
@@ -83,11 +94,12 @@ public final class VersionItem extends StackPane {
btnSettings.setOnMouseClicked(handler);
}
public void setOnDeleteButtonClicked(EventHandler<? super MouseEvent> handler) {
btnDelete.setOnMouseClicked(handler);
public void setOnScriptButtonClicked(EventHandler<? super MouseEvent> handler) {
btnScript.setOnMouseClicked(handler);
}
public void setOnLaunchButtonClicked(EventHandler<? super MouseEvent> handler) {
launchClickedHandler = handler;
btnLaunch.setOnMouseClicked(handler);
}
}

View File

@@ -82,7 +82,7 @@ public final class VersionPage extends StackPane implements DecoratorPage {
FXUtils.installTooltip(btnBrowseMenu, 0, 5000, 0, new Tooltip(Main.i18n("game_settings.exploration")));
FXUtils.installTooltip(btnManagementMenu, 0, 5000, 0, new Tooltip(Main.i18n("game_settings.management")));
FXUtils.installTooltip(btnExport, 0, 5000, 0, new Tooltip(Main.i18n("modpack.task.save")));
FXUtils.installTooltip(btnExport, 0, 5000, 0, new Tooltip(Main.i18n("modpack.export")));
}
public void load(String id, Profile profile) {
@@ -108,6 +108,12 @@ public final class VersionPage extends StackPane implements DecoratorPage {
managementPopup.show(btnManagementMenu, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, -12, 15);
}
public void onDelete() {
profile.getRepository().removeVersionFromDisk(version);
profile.getRepository().refreshVersions();
Controllers.navigate(null);
}
public void onExport() {
Controllers.getDecorator().startWizard(new ExportWizardProvider(profile, version), Main.i18n("modpack.wizard"));
}
@@ -145,7 +151,7 @@ public final class VersionPage extends StackPane implements DecoratorPage {
public void onManagement() {
switch (managementList.getSelectionModel().getSelectedIndex()) {
case 0: // rename a version
Optional<String> res = FXUtils.inputDialog("Input", Main.i18n("versions.manage.rename.message"), null, version);
Optional<String> res = FXUtils.inputDialog("Input", Main.i18n("version.manage.rename.message"), null, version);
if (res.isPresent()) {
if (profile.getRepository().renameVersion(version, res.get())) {
profile.getRepository().refreshVersions();
@@ -154,7 +160,7 @@ public final class VersionPage extends StackPane implements DecoratorPage {
}
break;
case 1: // remove a version
if (FXUtils.alert(Alert.AlertType.CONFIRMATION, "Confirm", Main.i18n("versions.manage.remove.confirm") + version)) {
if (FXUtils.alert(Alert.AlertType.CONFIRMATION, "Confirm", Main.i18n("version.manage.remove.confirm") + version)) {
if (profile.getRepository().removeVersionFromDisk(version)) {
profile.getRepository().refreshVersions();
Controllers.navigate(null);

View File

@@ -0,0 +1,55 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* 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 {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui.construct;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXDialog;
import com.jfoenix.controls.JFXTextField;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.ui.FXUtils;
import java.util.function.Consumer;
public class InputDialogPane extends StackPane {
private final String text;
private final JFXDialog dialog;
@FXML
private JFXButton acceptButton;
@FXML
private JFXButton cancelButton;
@FXML
private JFXTextField textField;
@FXML
private Label content;
public InputDialogPane(String text, JFXDialog dialog, Consumer<String> onResult) {
this.text = text;
this.dialog = dialog;
FXUtils.loadFXML(this, "/assets/fxml/input-dialog.fxml");
content.setText(text);
cancelButton.setOnMouseClicked(e -> dialog.close());
acceptButton.setOnMouseClicked(e -> {
onResult.accept(textField.getText());
dialog.close();
});
}
}

View File

@@ -15,13 +15,14 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui;
package org.jackhuang.hmcl.ui.construct;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXDialog;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.ui.FXUtils;
public final class MessageDialogPane extends StackPane {
private final String text;

View File

@@ -0,0 +1,146 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* 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 {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui.construct;
import com.jfoenix.controls.JFXProgressBar;
import javafx.application.Platform;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.download.forge.ForgeInstallTask;
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
import org.jackhuang.hmcl.download.game.GameAssetRefreshTask;
import org.jackhuang.hmcl.download.liteloader.LiteLoaderInstallTask;
import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask;
import org.jackhuang.hmcl.game.HMCLModpackExportTask;
import org.jackhuang.hmcl.game.HMCLModpackInstallTask;
import org.jackhuang.hmcl.mod.CurseCompletionTask;
import org.jackhuang.hmcl.mod.CurseInstallTask;
import org.jackhuang.hmcl.mod.MultiMCModpackInstallTask;
import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.task.TaskListener;
import org.jackhuang.hmcl.ui.AdvancedListBox;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
public final class TaskListPane extends StackPane {
private final TaskExecutor executor;
private final AdvancedListBox listBox = new AdvancedListBox();
private final Map<Task, ProgressListNode> nodes = new HashMap<>();
public TaskListPane(TaskExecutor executor, Runnable onTerminate) {
this.executor = executor;
listBox.setSpacing(0);
executor.addTaskListener(new TaskListener() {
@Override
public void onReady(Task task) {
if (!task.getSignificance().shouldShow())
return;
ProgressListNode node = new ProgressListNode(task);
nodes.put(task, node);
Platform.runLater(() -> listBox.add(node));
if (task instanceof GameAssetRefreshTask) {
task.setName(Main.i18n("assets.download"));
} else if (task instanceof GameAssetDownloadTask) {
task.setName(Main.i18n("assets.download_all"));
} else if (task instanceof ForgeInstallTask) {
task.setName(Main.i18n("install.installer.install", Main.i18n("install.installer.forge")));
} else if (task instanceof LiteLoaderInstallTask) {
task.setName(Main.i18n("install.installer.install", Main.i18n("install.installer.liteloader")));
} else if (task instanceof OptiFineInstallTask) {
task.setName(Main.i18n("install.installer.install", Main.i18n("install.installer.optifine")));
} else if (task instanceof CurseCompletionTask) {
task.setName(Main.i18n("modpack.type.curse.completion"));
} else if (task instanceof CurseInstallTask) {
task.setName(Main.i18n("modpack.install", Main.i18n("modpack.type.curse")));
} else if (task instanceof MultiMCModpackInstallTask) {
task.setName(Main.i18n("modpack.install", Main.i18n("modpack.type.curse")));
} else if (task instanceof HMCLModpackInstallTask) {
task.setName(Main.i18n("modpack.install", Main.i18n("modpack.type.curse")));
} else if (task instanceof HMCLModpackExportTask) {
task.setName(Main.i18n("modpack.export"));
}
}
@Override
public void onFinished(Task task) {
ProgressListNode node = nodes.remove(task);
if (node == null)
return;
node.unbind();
Platform.runLater(() -> listBox.remove(node));
}
@Override
public void onFailed(Task task, Throwable throwable) {
ProgressListNode node = nodes.remove(task);
if (node == null)
return;
Platform.runLater(() -> node.setThrowable(throwable));
}
@Override
public void onTerminate() {
Optional.ofNullable(onTerminate).ifPresent(Runnable::run);
}
});
getChildren().setAll(listBox);
}
private static class ProgressListNode extends StackPane {
private final JFXProgressBar bar = new JFXProgressBar();
private final Label title = new Label();
private final Label state = new Label();
public ProgressListNode(Task task) {
bar.progressProperty().bind(task.progressProperty());
title.setText(task.getName());
state.textProperty().bind(task.messageProperty());
BorderPane borderPane = new BorderPane();
borderPane.setLeft(title);
borderPane.setRight(state);
getChildren().addAll(bar, borderPane);
bar.setMinHeight(20);
bar.minWidthProperty().bind(widthProperty());
bar.prefWidthProperty().bind(widthProperty());
bar.maxWidthProperty().bind(widthProperty());
}
public void unbind() {
bar.progressProperty().unbind();
state.textProperty().unbind();
}
public void setThrowable(Throwable throwable) {
unbind();
state.setText(throwable.getLocalizedMessage());
bar.setProgress(0);
}
}
}

View File

@@ -26,6 +26,7 @@ import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.task.TaskListener;
import org.jackhuang.hmcl.ui.construct.TaskListPane;
import org.jackhuang.hmcl.util.Lang;
import java.util.Map;
@@ -91,45 +92,9 @@ public interface AbstractWizardDisplayer extends WizardDisplayer {
@Override
default void handleTask(Map<String, Object> settings, Task task) {
VBox vbox = new VBox();
JFXProgressBar progressBar = new JFXProgressBar();
Label label = new Label();
progressBar.setMaxHeight(10);
vbox.getChildren().addAll(progressBar, label);
StackPane root = new StackPane();
root.getChildren().add(vbox);
navigateTo(root, Navigation.NavigationDirection.FINISH);
AtomicInteger finishedTasks = new AtomicInteger(0);
TaskExecutor executor = task.with(Task.of(Schedulers.javafx(), this::navigateToSuccess)).executor(e -> new TaskListener() {
@Override
public void onReady(Task task) {
Platform.runLater(() -> progressBar.setProgress(finishedTasks.get() * 1.0 / e.getRunningTasks()));
}
@Override
public void onFinished(Task task) {
Platform.runLater(() -> {
label.setText(task.getName());
progressBar.setProgress(finishedTasks.incrementAndGet() * 1.0 / e.getRunningTasks());
});
}
@Override
public void onFailed(Task task, Throwable throwable) {
Platform.runLater(() -> {
label.setText(task.getName());
progressBar.setProgress(finishedTasks.incrementAndGet() * 1.0 / e.getRunningTasks());
});
}
@Override
public void onTerminate() {
Platform.runLater(AbstractWizardDisplayer.this::navigateToSuccess);
}
});
TaskExecutor executor = task.with(Task.of(Schedulers.javafx(), this::navigateToSuccess)).executor();
TaskListPane pane = new TaskListPane(executor, () -> Platform.runLater(AbstractWizardDisplayer.this::navigateToSuccess));
navigateTo(pane, Navigation.NavigationDirection.FINISH);
getCancelQueue().add(executor);
executor.start();
}

View File

@@ -103,7 +103,7 @@ public class WizardController implements Navigation {
if (result instanceof DeferredWizardResult) displayer.handleDeferredWizardResult(settings, ((DeferredWizardResult) result));
else if (result instanceof Summary) displayer.navigateTo(((Summary) result).getComponent(), NavigationDirection.NEXT);
else if (result instanceof Task) displayer.handleTask(settings, ((Task) result));
else throw new IllegalStateException("Unrecognized wizard result: " + result);
else if (result != null) throw new IllegalStateException("Unrecognized wizard result: " + result);
}
@Override

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import com.jfoenix.controls.JFXButton?>
<?import com.jfoenix.controls.JFXDialogLayout?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.StackPane?>
<?import com.jfoenix.controls.JFXTextField?>
<fx:root xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
type="StackPane">
<JFXDialogLayout>
<heading>
<Label id="content" />
</heading>
<body>
<JFXTextField fx:id="textField" />
</body>
<actions>
<JFXButton fx:id="cancelButton" styleClass="dialog-cancel" text="%button.cancel" />
<JFXButton fx:id="acceptButton" styleClass="dialog-accept" text="%button.ok" />
</actions>
</JFXDialogLayout>
</fx:root>

View File

@@ -6,6 +6,7 @@
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.control.Tooltip?>
<fx:root xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
type="StackPane" pickOnBounds="false">
@@ -28,12 +29,26 @@
<BorderPane>
<left>
<HBox spacing="8">
<JFXButton fx:id="btnSettings" styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30" prefWidth="30" prefHeight="30" />
<JFXButton fx:id="btnDelete" styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30" prefWidth="30" prefHeight="30" />
<JFXButton fx:id="btnSettings" styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30" prefWidth="30" prefHeight="30">
<tooltip>
<Tooltip text="%version.manage.settings" />
</tooltip>
</JFXButton>
</HBox>
</left>
<right>
<JFXButton fx:id="btnLaunch" styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minHeight="30" minWidth="30" prefWidth="30" prefHeight="30" />
<HBox spacing="8">
<JFXButton fx:id="btnScript" styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30" prefWidth="30" prefHeight="30">
<tooltip>
<Tooltip text="%version.launch_script" />
</tooltip>
</JFXButton>
<JFXButton fx:id="btnLaunch" styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minHeight="30" minWidth="30" prefWidth="30" prefHeight="30">
<tooltip>
<Tooltip text="%version.launch" />
</tooltip>
</JFXButton>
</HBox>
</right>
</BorderPane>

View File

@@ -23,6 +23,12 @@
</JFXTabPane>
<HBox alignment="TOP_RIGHT" style="-fx-padding: 3px;" pickOnBounds="false">
<JFXButton fx:id="btnDelete" maxHeight="28.0" minHeight="28.0" onMouseClicked="#onDelete"
styleClass="toggle-icon3" ripplerFill="white">
<graphic>
<fx:include source="/assets/svg/delete-black.fxml"/>
</graphic>
</JFXButton>
<JFXButton fx:id="btnExport" maxHeight="28.0" minHeight="28.0" onMouseClicked="#onExport"
styleClass="toggle-icon3" ripplerFill="white">
<graphic>
@@ -57,9 +63,9 @@
<JFXListView fx:id="managementList" styleClass="option-list-view" onMouseClicked="#onManagement"
maxWidth="300.0" minWidth="300.0">
<Label text="%versions.manage.rename"/>
<Label text="%versions.manage.remove"/>
<Label text="%versions.manage.redownload_assets_index"/>
<Label text="%versions.manage.remove_libraries"/>
<Label text="%version.manage.rename"/>
<Label text="%version.manage.remove"/>
<Label text="%version.manage.redownload_assets_index"/>
<Label text="%version.manage.remove_libraries"/>
</JFXListView>
</fx:root>

View File

@@ -7,6 +7,7 @@
<?import javafx.scene.layout.*?>
<fx:root xmlns="http://javafx.com/javafx"
type="StackPane"
style="-fx-background-color: gray;"
xmlns:fx="http://javafx.com/fxml">
<VBox>
<JFXToolbar fx:id="toolbar" styleClass="jfx-tool-bar">

View File

@@ -17,17 +17,17 @@
#author: huangyuhui, dxNeil
launch.failed=Failed to launch.
launch.failed_creating_process=Failed to create process, maybe your java path is wrong, please modify your java path.
launch.failed_sh_permission=Failed to add permission to the launch script.
launch.failed_packing_jar=Failed to pack the jar.
launch.failed.sh_permission=Failed to add permission to the launch script.
launch.failed.packing_jar=Failed to pack the jar.
launch.unsupported_launcher_version=Sorry, the launcher cannot launch minecraft, but will retry launching it.
launch.too_big_memory_alloc_64bit=You have allocated too much memory, because of your 32-Bit Java Runtime Environment, your game probably crash. The maximum memory is 1024MB. The launcher will try to launch it.
launch.too_big_memory_alloc_free_space_too_low=You have allocated too much memory, because the physical memory size is %dMB, your game probably crash. The launcher will try to launch it.
launch.cannot_create_jvm=We find that it cannot create java virutal machine. The Java argements may have problems. You can enable the no args mode in the settings.
launch.advice.too_large_memory_for_32bit=You have allocated too much memory, because of your 32-Bit Java Runtime Environment, your game probably crash. The maximum memory is 1024MB. The launcher will try to launch it.
launch.advice.not_enough_space=You have allocated too much memory, because the physical memory size is %dMB, your game probably crash. The launcher will try to launch it.
launch.failed.cannot_create_jvm=We find that it cannot create java virutal machine. The Java argements may have problems. You can enable the no args mode in the settings.
launch.circular_dependency_versions=Found circular dependency versions, please check if your client has been modified.
launch.not_finished_downloading_libraries=Did not finish downloading libraries, continue launching game?
launch.not_finished_decompressing_natives=Did not finish decompressing native libraries, continue launching game?
launch.failed.downloading_libraries=Did not finish downloading libraries, continue launching game?
launch.failed.decompressing_natives=Did not finish decompressing native libraries, continue launching game?
launch.wrong_javadir=Incorrect Java directory, will reset to default Java directory.
launch.exited_abnormally=Game exited abnormally, please visit the log, or ask someone for help.
launch.failed.exited_abnormally=Game exited abnormally, please visit the log, or ask someone for help.
launch.state.logging_in=Logging In
launch.state.generating_launching_codes=Generating Launching Codes
@@ -111,9 +111,9 @@ ui.label.newProfileWindow.copy_from=Copy From:
ui.newProfileWindow.title=New Config
ui.button.ok=OK
ui.button.refresh=Refresh
ui.button.run=Play
ui.button.settings=Settings
ui.button.refresh=Refresh
version.launch=Play
version.manage.settings=Settings
ui.button.about=About
ui.button.others=Others
ui.button.logout=Log Out
@@ -208,8 +208,8 @@ modpack.export_finished=Exporting the modpack finished. See
modpack.included_launcher=The modpack is included the launcher, you can publish it directly.
modpack.not_included_launcher=You can use the modpack by clicking the "Import Modpack" button.
modpack.enter_name=Enter your desired name for this game.
modpack.task.save=Export Modpack
modpack.author=Author
modpack.export=Export Modpack
modpack.task.install=Import Modpack
modpack.task.install.error=Failed to install the modpack. Maybe the files is incorrect, or a management issue occurred.
modpack.task.install.will=Install the modpack:
@@ -274,10 +274,10 @@ advancedsettings.server_ip=Server Host
advancedsettings.dont_check_game_completeness=Don't check game completeness
mainwindow.show_log=Show Logs
mainwindow.make_launch_script=Make Launching Script.
mainwindow.make_launch_script_failed=Failed to make script.
version.launch_script=Make Launching Script.
version.launch_script.failed=Failed to make script.
mainwindow.enter_script_name=Enter the script name.
mainwindow.make_launch_succeed=Finished script creation.
version.launch_script.success=Finished script creation, %s.
mainwindow.no_version=No version found. Switch to Game Downloads Tab?
launcher.about=About Author\nMinecraft Forum ID: klkl6523\nCopyright (c) 2013 huangyuhui\nOpened source under GPL v3 license:http://github.com/huanghongxun/HMCL/\nThis software used project Gson which is under Apache License 2.0, thanks contributors.
@@ -308,21 +308,21 @@ game_settings=Games
main_page=Home
launcher_settings=Launcher
versions.release=Release
versions.snapshot=Snapshot
versions.old_beta=Beta
versions.old_alpha=Old Alpha
version.game.release=Release
version.game.snapshot=Snapshot
version.game.old_beta=Beta
version.game.old_alpha=Old Alpha
versions.manage.rename=Rename this version
versions.manage.rename.message=Please enter the new name
versions.manage.remove=Delete this version
versions.manage.remove.confirm=Sure to remove the version
versions.manage.redownload_json=Redownload Minecraft Configuration(minecraft.json)
versions.manage.redownload_assets_index=Redownload Assets Index
versions.manage.remove_libraries=Delete library files
version.manage.rename=Rename this version
version.manage.rename.message=Please enter the new name
version.manage.remove=Delete this version
version.manage.remove.confirm=Sure to remove the version
version.manage.redownload_json=Redownload Minecraft Configuration(minecraft.json)
version.manage.redownload_assets_index=Redownload Assets Index
version.manage.remove_libraries=Delete library files
advice.os64butjdk32=Your OS is 64-Bit but your Java is 32-Bit. The 64-Bit Java is recommended.
advice.java8=Java 8 is suggested, which can make game run more fluently. And many mods and Minecraft 1.12 and newer versions requires Java 8.
launch.advice.different_platform=Your OS is 64-Bit but your Java is 32-Bit. The 64-Bit Java is recommended.
launch.advice.newer_java=Java 8 is suggested, which can make game run more fluently. And many mods and Minecraft 1.12 and newer versions requires Java 8.
assets.download_all=Download Assets Files
assets.failed=Failed to get the list, try again.
@@ -407,3 +407,9 @@ install.select=Select an operation
install=Install New Game
settings.icon=Game Icon
launcher=Launcher
install.installer.install=
modpack.install=Install %s modpack
modpack.type.curse=Curse
modpack.type.multimc=MultiMC
modpack.type.hmcl=HMCL
modpack.type.curse.completion=Install relative files to Curse modpack

View File

@@ -17,17 +17,17 @@
#author: huangyuhui
launch.failed=启动失败
launch.failed_creating_process=启动失败在创建新进程时发生错误可能是Java路径错误。
launch.failed_sh_permission=为启动文件添加权限时发生错误
launch.failed_packing_jar=在打包jar时发生错误
launch.failed.sh_permission=为启动文件添加权限时发生错误
launch.failed.packing_jar=在打包jar时发生错误
launch.unsupported_launcher_version=对不起本启动器现在可能不能启动这个版本的Minecraft但启动器还是会尝试启动请尽快将此问题报告给作者。
launch.too_big_memory_alloc_64bit=您设置的内存大小过大由于可能超过了32位Java的内存分配限制所以可能无法启动游戏请将内存调至1024MB或更小启动器仍会尝试启动。
launch.too_big_memory_alloc_free_space_too_low=您设置的内存大小过大,由于超过了系统内存大小%dMB所以可能影响游戏体验或无法启动游戏启动器仍会尝试启动。
launch.cannot_create_jvm=截获到无法创建Java虚拟机可能是Java参数有问题可以在设置中开启无参数模式启动
launch.advice.too_large_memory_for_32bit=您设置的内存大小过大由于可能超过了32位Java的内存分配限制所以可能无法启动游戏请将内存调至1024MB或更小启动器仍会尝试启动。
launch.advice.not_enough_space=您设置的内存大小过大,由于超过了系统内存大小%dMB所以可能影响游戏体验或无法启动游戏启动器仍会尝试启动。
launch.failed.cannot_create_jvm=截获到无法创建Java虚拟机可能是Java参数有问题可以在设置中开启无参数模式启动
launch.circular_dependency_versions=发现游戏版本循环引用,请确认您的客户端未被修改或修改导致出现此问题。
launch.not_finished_downloading_libraries=未完成游戏依赖库的下载,还要继续启动游戏吗?
launch.not_finished_decompressing_natives=未能解压游戏本地库,还要继续启动游戏吗?
launch.failed.downloading_libraries=未完成游戏依赖库的下载,还要继续启动游戏吗?
launch.failed.decompressing_natives=未能解压游戏本地库,还要继续启动游戏吗?
launch.wrong_javadir=错误的Java路径将自动重置为默认Java路径。
launch.exited_abnormally=游戏非正常退出,请查看日志文件,或联系他人寻求帮助。
launch.failed.exited_abnormally=游戏非正常退出,请查看日志文件,或联系他人寻求帮助。
launch.state.logging_in=登录中
launch.state.generating_launching_codes=正在生成启动代码
@@ -112,8 +112,8 @@ ui.newProfileWindow.title=新建配置
ui.button.ok=确认
ui.button.refresh=刷新
ui.button.run=启动Minecraft
ui.button.settings=
version.launch=启动游戏
version.manage.settings=游戏设置
ui.button.about=关于
ui.button.others=其他
ui.button.logout=登出
@@ -209,7 +209,7 @@ modpack.not_included_launcher=整合包未包含启动器,可使用本软件
modpack.enter_name=给游戏起个你喜欢的名字
modpack.author=作者
modpack.task.save=导出整合包
modpack.export=导出整合包
modpack.task.install=导入整合包
modpack.task.install.error=安装失败,可能是整合包格式不正确或操作文件失败
modpack.task.install.will=将会安装整合包:
@@ -274,10 +274,10 @@ advancedsettings.server_ip=直入服务器ip地址(不必填写,启动游戏
advancedsettings.dont_check_game_completeness=不检查游戏完整性
mainwindow.show_log=查看日志
mainwindow.make_launch_script=生成启动脚本
mainwindow.make_launch_script_failed=生成启动脚本失败
version.launch_script=生成启动脚本
version.launch_script.failed=生成启动脚本失败
mainwindow.enter_script_name=输入要生成脚本的文件名
mainwindow.make_launch_succeed=启动脚本已生成完毕:
version.launch_script.success=启动脚本已生成完毕: %s.
mainwindow.no_version=未找到任何版本,是否进入游戏下载?
launcher.about=默认背景图感谢gamerteam提供。\n关于作者\n百度IDhuanghongxun20\nmcbbshuanghongxun\nMinecraft Forum ID: klkl6523\n欢迎提交Bug哦\nCopyright (c) 2013-2017 huangyuhui.\n免责声明Minecraft软件版权归Mojang AB所有使用本软件产生的版权问题本软件制作方概不负责。\n本启动器在GPLv3协议下开源:https://github.com/huanghongxun/HMCL/ ,感谢issues和pull requests贡献者\n本软件使用了基于Apache License 2.0的Gson项目感谢贡献者。
@@ -308,21 +308,21 @@ game_settings=游戏设置
main_page=主页
launcher_settings=启动器设置
versions.release=稳定版
versions.snapshot=快照版
versions.old_beta=测试版
versions.old_alpha=远古版
version.game.release=稳定版
version.game.snapshot=快照版
version.game.old_beta=测试版
version.game.old_alpha=远古版
versions.manage.rename=重命名该版本
versions.manage.rename.message=请输入要改成的名字
versions.manage.remove=删除该版本
versions.manage.remove.confirm=真的要删除版本
versions.manage.redownload_json=重新下载版本配置(minecraft.json)
versions.manage.redownload_assets_index=重新下载资源配置(assets_index.json)
versions.manage.remove_libraries=删除所有库文件
version.manage.rename=重命名该版本
version.manage.rename.message=请输入要改成的名字
version.manage.remove=删除该版本
version.manage.remove.confirm=真的要删除版本
version.manage.redownload_json=重新下载版本配置(minecraft.json)
version.manage.redownload_assets_index=重新下载资源配置(assets_index.json)
version.manage.remove_libraries=删除所有库文件
advice.os64butjdk32=您的系统是64位的但是Java是32位的推荐您安装64位Java.
advice.java8=检测到您未使用Java 8及更新版本Java 8能使游戏更流畅而且Minecraft 1.12及更新版本和很多Mod强制需要需要Java 8。
launch.advice.different_platform=您的系统是64位的但是Java是32位的推荐您安装64位Java.
launch.advice.newer_java=检测到您未使用Java 8及更新版本Java 8能使游戏更流畅而且Minecraft 1.12及更新版本和很多Mod强制需要需要Java 8。
assets.download_all=下载资源文件
assets.failed=获取列表失败,请刷新重试。
@@ -407,3 +407,9 @@ install.select=请选择安装方式
install=添加游戏
settings.icon=游戏图标
launcher=启动器
install.installer.install=安装%s
modpack.install=安装%s整合包
modpack.type.curse=Curse
modpack.type.multimc=MultiMC
modpack.type.hmcl=HMCL
modpack.type.curse.completion=下载Curse整合包相关文件

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.shape.SVGPath?>
<javafx.scene.shape.SVGPath fill="white" content="M14,20A2,2 0 0,0 16,18V5H9A1,1 0 0,0 8,6V16H5V5A3,3 0 0,1 8,2H19A3,3 0 0,1 22,5V6H18V18L18,19A3,3 0 0,1 15,22H5A3,3 0 0,1 2,19V18H12A2,2 0 0,0 14,20Z" />

View File

@@ -23,6 +23,7 @@ import org.jackhuang.hmcl.task.ParallelTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.AutoTypingMap;
import org.jackhuang.hmcl.util.Constants;
import org.jackhuang.hmcl.util.ExceptionalFunction;
import java.util.function.Function;
@@ -63,7 +64,7 @@ public class DefaultGameBuilder extends GameBuilder {
});
}
private Function<AutoTypingMap<String>, Task> libraryTaskHelper(String gameVersion, String libraryId) {
private ExceptionalFunction<AutoTypingMap<String>, Task, ?> libraryTaskHelper(String gameVersion, String libraryId) {
return variables -> dependencyManager.installLibraryAsync(gameVersion, variables.get("version"), libraryId, toolVersions.get(libraryId));
}

View File

@@ -50,6 +50,7 @@ public final class GameAssetIndexDownloadTask extends Task {
public GameAssetIndexDownloadTask(AbstractDependencyManager dependencyManager, Version version) {
this.dependencyManager = dependencyManager;
this.version = version;
setSignificance(TaskSignificance.MODERATE);
}
@Override

View File

@@ -39,6 +39,8 @@ public final class GameDownloadTask extends Task {
public GameDownloadTask(DefaultDependencyManager dependencyManager, Version version) {
this.dependencyManager = dependencyManager;
this.version = version;
setSignificance(TaskSignificance.MODERATE);
}
@Override

View File

@@ -49,6 +49,7 @@ public final class GameLibrariesTask extends Task {
public GameLibrariesTask(AbstractDependencyManager dependencyManager, Version version) {
this.dependencyManager = dependencyManager;
this.version = version;
setSignificance(TaskSignificance.MODERATE);
}
@Override

View File

@@ -50,6 +50,7 @@ public final class GameLoggingDownloadTask extends Task {
public GameLoggingDownloadTask(DependencyManager dependencyManager, Version version) {
this.dependencyManager = dependencyManager;
this.version = version;
setSignificance(TaskSignificance.MODERATE);
}
@Override

View File

@@ -47,6 +47,8 @@ public final class VersionJsonDownloadTask extends Task {
if (!gameVersionList.isLoaded())
dependents.add(gameVersionList.refreshAsync(dependencyManager.getDownloadProvider()));
setSignificance(TaskSignificance.MODERATE);
}
@Override

View File

@@ -45,6 +45,8 @@ public final class VersionJsonSaveTask extends Task {
public VersionJsonSaveTask(DefaultGameRepository repository, Version version) {
this.repository = repository;
this.version = version;
setSignificance(TaskSignificance.MODERATE);
}
@Override

View File

@@ -18,6 +18,7 @@
package org.jackhuang.hmcl.task;
import org.jackhuang.hmcl.util.AutoTypingMap;
import org.jackhuang.hmcl.util.ExceptionalFunction;
import java.util.Collection;
import java.util.Collections;
@@ -35,7 +36,7 @@ final class CoupleTask<P extends Task> extends Task {
private final boolean relyingOnDependents;
private final Collection<Task> dependents;
private final List<Task> dependencies = new LinkedList<>();
private final Function<AutoTypingMap<String>, Task> succ;
private final ExceptionalFunction<AutoTypingMap<String>, Task, ?> succ;
/**
* A task that combines two tasks and make sure pred runs before succ.
@@ -44,24 +45,21 @@ final class CoupleTask<P extends Task> extends Task {
* @param succ a callback that returns the task runs after pred, succ will be executed asynchronously. You can do something that relies on the result of pred.
* @param relyingOnDependents true if this task chain will be broken when task pred fails.
*/
public CoupleTask(P pred, Function<AutoTypingMap<String>, Task> succ, boolean relyingOnDependents) {
public CoupleTask(P pred, ExceptionalFunction<AutoTypingMap<String>, Task, ?> succ, boolean relyingOnDependents) {
this.dependents = Collections.singleton(pred);
this.succ = succ;
this.relyingOnDependents = relyingOnDependents;
setSignificance(TaskSignificance.MODERATE);
}
@Override
public void execute() {
public void execute() throws Exception {
Task task = succ.apply(getVariables());
if (task != null)
dependencies.add(task);
}
@Override
public boolean isHidden() {
return true;
}
@Override
public Collection<Task> getDependents() {
return dependents;

View File

@@ -88,6 +88,8 @@ public class FileDownloadTask extends Task {
this.hash = hash;
this.retry = retry;
this.proxy = proxy;
setName(file.getName());
}
private void closeFiles() {
@@ -101,6 +103,10 @@ public class FileDownloadTask extends Task {
return onFailed;
}
public URL getUrl() {
return url;
}
@Override
public void execute() throws Exception {
URL currentURL = url;

View File

@@ -65,6 +65,8 @@ public final class GetTask extends TaskResult<String> {
this.retry = retry;
this.proxy = proxy;
this.id = id;
setName(url.toString());
}
@Override

View File

@@ -37,17 +37,13 @@ public final class ParallelTask extends Task {
*/
public ParallelTask(Task... tasks) {
this.tasks = tasks;
setSignificance(TaskSignificance.MINOR);
}
@Override
public void execute() {
}
@Override
public boolean isHidden() {
return true;
}
@Override
public Collection<Task> getDependents() {
return Arrays.asList(tasks);

View File

@@ -22,10 +22,7 @@ import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import org.jackhuang.hmcl.event.EventManager;
import org.jackhuang.hmcl.util.AutoTypingMap;
import org.jackhuang.hmcl.util.ExceptionalConsumer;
import org.jackhuang.hmcl.util.ExceptionalRunnable;
import org.jackhuang.hmcl.util.Properties;
import org.jackhuang.hmcl.util.*;
import java.util.Collection;
import java.util.Collections;
@@ -42,11 +39,17 @@ public abstract class Task {
private final EventManager<TaskEvent> onDone = new EventManager<>();
private TaskSignificance significance = TaskSignificance.MAJOR;
/**
* True if not logging when executing this task.
*/
public boolean isHidden() {
return false;
public final TaskSignificance getSignificance() {
return significance;
}
public void setSignificance(TaskSignificance significance) {
this.significance = significance;
}
/**
@@ -139,10 +142,10 @@ public abstract class Task {
}
protected void updateProgress(double progress) {
if (progress > 1.0)
if (progress < 0 || progress > 1.0)
throw new IllegalArgumentException("Progress is must between 0 and 1.");
long now = System.currentTimeMillis();
if (now - lastTime >= getProgressInterval()) {
if (lastTime == Long.MIN_VALUE || now - lastTime >= getProgressInterval()) {
updateProgressImmediately(progress);
lastTime = now;
}
@@ -186,7 +189,7 @@ public abstract class Task {
public final TaskExecutor executor(Function<TaskExecutor, TaskListener> taskListener) {
TaskExecutor executor = new TaskExecutor(this);
executor.setTaskListener(taskListener.apply(executor));
executor.addTaskListener(taskListener.apply(executor));
return executor;
}
@@ -224,7 +227,7 @@ public abstract class Task {
return then(s -> b);
}
public final Task then(Function<AutoTypingMap<String>, Task> b) {
public final Task then(ExceptionalFunction<AutoTypingMap<String>, Task, ?> b) {
return new CoupleTask<>(this, b, true);
}
@@ -232,7 +235,7 @@ public abstract class Task {
return with(s -> b);
}
public final Task with(Function<AutoTypingMap<String>, Task> b) {
public final Task with(ExceptionalFunction<AutoTypingMap<String>, Task, ?> b) {
return new CoupleTask<>(this, b, false);
}
@@ -264,4 +267,18 @@ public abstract class Task {
public static <V> TaskResult<V> ofResult(String id, Function<AutoTypingMap<String>, V> closure) {
return new TaskCallable2<>(id, closure);
}
public enum TaskSignificance {
MAJOR,
MODERATE,
MINOR;
public boolean shouldLog() {
return this != MINOR;
}
public boolean shouldShow() {
return this == MAJOR;
}
}
}

View File

@@ -22,13 +22,11 @@ import org.jackhuang.hmcl.util.ExceptionalRunnable;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.Logging;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Objects;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
@@ -40,7 +38,7 @@ import java.util.logging.Level;
public final class TaskExecutor {
private final Task firstTask;
private TaskListener taskListener = TaskListener.DefaultTaskListener.INSTANCE;
private List<TaskListener> taskListeners = new LinkedList<>();
private boolean canceled = false;
private Exception lastException;
private final AtomicInteger totTask = new AtomicInteger(0);
@@ -52,15 +50,8 @@ public final class TaskExecutor {
this.firstTask = task;
}
public TaskListener getTaskListener() {
return taskListener;
}
public void setTaskListener(TaskListener taskListener) {
if (taskListener == null)
this.taskListener = TaskListener.DefaultTaskListener.INSTANCE;
else
this.taskListener = taskListener;
public void addTaskListener(TaskListener taskListener) {
taskListeners.add(taskListener);
}
public boolean isCanceled() {
@@ -78,7 +69,10 @@ public final class TaskExecutor {
public void start() {
workerQueue.add(scheduler.schedule(() -> {
if (!executeTasks(Collections.singleton(firstTask)))
taskListener.onTerminate();
taskListeners.forEach(it -> {
it.onTerminate();
it.onTerminate(variables);
});
}));
}
@@ -86,7 +80,10 @@ public final class TaskExecutor {
AtomicBoolean flag = new AtomicBoolean(true);
Future<?> future = scheduler.schedule(() -> {
if (!executeTasks(Collections.singleton(firstTask))) {
taskListener.onTerminate();
taskListeners.forEach(it -> {
it.onTerminate();
it.onTerminate(variables);
});
flag.set(false);
}
});
@@ -108,7 +105,7 @@ public final class TaskExecutor {
}
}
private boolean executeTasks(Collection<Task> tasks) {
private boolean executeTasks(Collection<Task> tasks) throws InterruptedException {
if (tasks.isEmpty())
return true;
@@ -119,38 +116,35 @@ public final class TaskExecutor {
if (canceled)
return false;
Invoker invoker = new Invoker(task, latch, success);
Future<?> future = task.getScheduler().schedule(invoker);
if (future != null)
workerQueue.add(future);
try {
Future<?> future = task.getScheduler().schedule(invoker);
if (future != null)
workerQueue.add(future);
} catch (RejectedExecutionException e) {
throw new InterruptedException();
}
}
if (canceled)
return false;
try {
latch.await();
return success.get() && !canceled;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
// Once interrupted, we are aborting the subscription.
// and operations fail.
return false;
}
latch.await();
return success.get() && !canceled;
}
private boolean executeTask(Task task) {
if (canceled)
return false;
if (!task.isHidden())
if (task.getSignificance().shouldLog())
Logging.LOG.log(Level.FINE, "Executing task: {0}", task.getName());
taskListener.onReady(task);
taskListeners.forEach(it -> it.onReady(task));
boolean doDependentsSucceeded = executeTasks(task.getDependents());
boolean flag = false;
try {
boolean doDependentsSucceeded = executeTasks(task.getDependents());
if (!doDependentsSucceeded && task.isRelyingOnDependents() || canceled)
throw new SilentException();
@@ -166,27 +160,29 @@ public final class TaskExecutor {
throw new IllegalStateException("Subtasks failed for " + task.getName());
flag = true;
if (!task.isHidden()) {
if (task.getSignificance().shouldLog()) {
Logging.LOG.log(Level.FINER, "Task finished: {0}", task.getName());
task.onDone().fireEvent(new TaskEvent(this, task, false));
taskListener.onFinished(task);
taskListeners.forEach(it -> it.onFinished(task));
}
} catch (InterruptedException e) {
if (!task.isHidden()) {
if (task.getSignificance().shouldLog()) {
lastException = e;
Logging.LOG.log(Level.FINE, "Task aborted: " + task.getName(), e);
task.onDone().fireEvent(new TaskEvent(this, task, true));
taskListener.onFailed(task, e);
taskListeners.forEach(it -> it.onFailed(task, e));
}
} catch (SilentException e) {
// do nothing
} catch (RejectedExecutionException e) {
return false;
} catch (Exception e) {
if (!task.isHidden()) {
if (task.getSignificance().shouldLog()) {
lastException = e;
Logging.LOG.log(Level.FINE, "Task failed: " + task.getName(), e);
task.onDone().fireEvent(new TaskEvent(this, task, true));
taskListener.onFailed(task, e);
taskListeners.forEach(it -> it.onFailed(task, e));
}
} finally {
task.setVariables(null);

View File

@@ -17,6 +17,8 @@
*/
package org.jackhuang.hmcl.task;
import org.jackhuang.hmcl.util.AutoTypingMap;
import java.util.EventListener;
/**
@@ -37,6 +39,9 @@ public abstract class TaskListener implements EventListener {
public void onTerminate() {
}
public void onTerminate(AutoTypingMap<String> variables) {
}
public static class DefaultTaskListener extends TaskListener {
public static final DefaultTaskListener INSTANCE = new DefaultTaskListener();