Compare commits
26
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d8add6d54 | ||
|
|
7ab22b0e07 | ||
|
|
20249ec4bf | ||
|
|
db38b4d532 | ||
|
|
ba5e63395f | ||
|
|
55cbb6cd2c | ||
|
|
8aecbe89f9 | ||
|
|
0a0476b6d3 | ||
|
|
38612e5d6c | ||
|
|
66e75fdc9d | ||
|
|
1e76733564 | ||
|
|
370d0ce78b | ||
|
|
6f402cd218 | ||
|
|
e778d223a7 | ||
|
|
db5aa0c869 | ||
|
|
8077b0bae1 | ||
|
|
e8c6c07e6a | ||
|
|
c1396c05aa | ||
|
|
9ca95c7f17 | ||
|
|
fb2d53664b | ||
|
|
6fd6022210 | ||
|
|
83782f5628 | ||
|
|
a19f147a6a | ||
|
|
85ce3d9a7a | ||
|
|
886a1f1207 | ||
|
|
8d6245eb38 |
No files matched your search
@@ -50,7 +50,6 @@ public final class EntryPoint {
|
||||
LOG.start(Metadata.HMCL_CURRENT_DIRECTORY.resolve("logs"));
|
||||
|
||||
setupJavaFXVMOptions();
|
||||
checkDirectoryPath();
|
||||
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS && !isInsideMacAppBundle())
|
||||
initIcon();
|
||||
@@ -192,16 +191,6 @@ public final class EntryPoint {
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkDirectoryPath() {
|
||||
String currentDir = System.getProperty("user.dir", "");
|
||||
if (currentDir.contains("!")) {
|
||||
LOG.error("The current working path contains an exclamation mark: " + currentDir);
|
||||
// No Chinese translation because both Swing and JavaFX cannot render Chinese character properly when exclamation mark exists in the path.
|
||||
showErrorAndExit("Exclamation mark(!) is not allowed in the path where HMCL is in.\n"
|
||||
+ "The path is " + currentDir);
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkJavaFX() {
|
||||
try {
|
||||
SelfDependencyPatcher.patch();
|
||||
|
||||
@@ -253,7 +253,7 @@ public final class Launcher extends Application {
|
||||
if (Files.exists(mcDir))
|
||||
files.add(mcDir.toString());
|
||||
|
||||
String command = new CommandBuilder().add("sudo", "chown", "-R", userName).addAll(files).toString();
|
||||
String command = new CommandBuilder().addAll("sudo", "chown", "-R", userName).addAll(files).toString();
|
||||
ButtonType copyAndExit = new ButtonType(i18n("button.copy_and_exit"));
|
||||
|
||||
if (showAlert(AlertType.ERROR,
|
||||
|
||||
@@ -46,6 +46,7 @@ import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Proxy;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
@@ -321,6 +322,8 @@ public final class HMCLGameRepository extends DefaultGameRepository {
|
||||
return VersionIconType.FABRIC.getIcon();
|
||||
else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.QUILT))
|
||||
return VersionIconType.QUILT.getIcon();
|
||||
else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.LEGACY_FABRIC))
|
||||
return VersionIconType.LEGACY_FABRIC.getIcon();
|
||||
else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.NEO_FORGE))
|
||||
return VersionIconType.NEO_FORGE.getIcon();
|
||||
else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.FORGE))
|
||||
@@ -419,6 +422,7 @@ public final class HMCLGameRepository extends DefaultGameRepository {
|
||||
.setHeight(vs.getHeight())
|
||||
.setFullscreen(vs.isFullscreen())
|
||||
.setWrapper(vs.getWrapper())
|
||||
.setProxyOption(getProxyOption())
|
||||
.setPreLaunchCommand(vs.getPreLaunchCommand())
|
||||
.setPostExitCommand(vs.getPostExitCommand())
|
||||
.setNoGeneratedJVMArgs(vs.isNoJVMArgs())
|
||||
@@ -438,17 +442,6 @@ public final class HMCLGameRepository extends DefaultGameRepository {
|
||||
builder.setQuickPlayOption(new QuickPlayOption.MultiPlayer(vs.getServerIp()));
|
||||
}
|
||||
|
||||
if (config().hasProxy()) {
|
||||
builder.setProxyType(config().getProxyType());
|
||||
builder.setProxyHost(config().getProxyHost());
|
||||
builder.setProxyPort(config().getProxyPort());
|
||||
|
||||
if (config().hasProxyAuth()) {
|
||||
builder.setProxyUser(config().getProxyUser());
|
||||
builder.setProxyPass(config().getProxyPass());
|
||||
}
|
||||
}
|
||||
|
||||
Path json = getModpackConfiguration(version);
|
||||
if (Files.exists(json)) {
|
||||
try {
|
||||
@@ -558,4 +551,39 @@ public final class HMCLGameRepository extends DefaultGameRepository {
|
||||
return minimum;
|
||||
}
|
||||
}
|
||||
|
||||
public static ProxyOption getProxyOption() {
|
||||
if (!config().hasProxy() || config().getProxyType() == null) {
|
||||
return ProxyOption.Default.INSTANCE;
|
||||
}
|
||||
|
||||
return switch (config().getProxyType()) {
|
||||
case DIRECT -> ProxyOption.Direct.INSTANCE;
|
||||
case HTTP, SOCKS -> {
|
||||
String proxyHost = config().getProxyHost();
|
||||
int proxyPort = config().getProxyPort();
|
||||
|
||||
if (StringUtils.isBlank(proxyHost) || proxyPort < 0 || proxyPort > 0xFFFF) {
|
||||
yield ProxyOption.Default.INSTANCE;
|
||||
}
|
||||
|
||||
String proxyUser = config().getProxyUser();
|
||||
String proxyPass = config().getProxyPass();
|
||||
|
||||
if (StringUtils.isBlank(proxyUser)) {
|
||||
proxyUser = null;
|
||||
proxyPass = null;
|
||||
} else if (proxyPass == null) {
|
||||
proxyPass = "";
|
||||
}
|
||||
|
||||
if (config().getProxyType() == Proxy.Type.HTTP) {
|
||||
yield new ProxyOption.Http(proxyHost, proxyPort, proxyUser, proxyPass);
|
||||
} else {
|
||||
yield new ProxyOption.Socks(proxyHost, proxyPort, proxyUser, proxyPass);
|
||||
}
|
||||
}
|
||||
default -> ProxyOption.Default.INSTANCE;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -389,7 +389,7 @@ public final class LauncherHelper {
|
||||
int targetJavaVersionMajor = Integer.parseInt(setting.getJavaVersion());
|
||||
GameJavaVersion minimumJavaVersion = GameJavaVersion.getMinimumJavaVersion(gameVersion);
|
||||
|
||||
if (minimumJavaVersion != null && targetJavaVersionMajor < minimumJavaVersion.getMajorVersion()) {
|
||||
if (minimumJavaVersion != null && targetJavaVersionMajor < minimumJavaVersion.majorVersion()) {
|
||||
Controllers.dialog(
|
||||
i18n("launch.failed.java_version_too_low"),
|
||||
i18n("message.error"),
|
||||
@@ -634,7 +634,7 @@ public final class LauncherHelper {
|
||||
private static CompletableFuture<JavaRuntime> downloadJava(GameJavaVersion javaVersion, Profile profile) {
|
||||
CompletableFuture<JavaRuntime> future = new CompletableFuture<>();
|
||||
Controllers.dialog(new MessageDialogPane.Builder(
|
||||
i18n("launch.advice.require_newer_java_version", javaVersion.getMajorVersion()),
|
||||
i18n("launch.advice.require_newer_java_version", javaVersion.majorVersion()),
|
||||
i18n("message.warning"),
|
||||
MessageType.QUESTION)
|
||||
.yesOrNo(() -> {
|
||||
|
||||
@@ -30,7 +30,9 @@ import org.jackhuang.hmcl.util.platform.Platform;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
@@ -57,7 +59,7 @@ public final class HMCLJavaRepository implements JavaRepository {
|
||||
}
|
||||
|
||||
public Path getJavaDir(Platform platform, GameJavaVersion gameJavaVersion) {
|
||||
return getJavaDir(platform, MOJANG_JAVA_PREFIX + gameJavaVersion.getComponent());
|
||||
return getJavaDir(platform, MOJANG_JAVA_PREFIX + gameJavaVersion.component());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -66,7 +68,7 @@ public final class HMCLJavaRepository implements JavaRepository {
|
||||
}
|
||||
|
||||
public Path getManifestFile(Platform platform, GameJavaVersion gameJavaVersion) {
|
||||
return getManifestFile(platform, MOJANG_JAVA_PREFIX + gameJavaVersion.getComponent());
|
||||
return getManifestFile(platform, MOJANG_JAVA_PREFIX + gameJavaVersion.component());
|
||||
}
|
||||
|
||||
public boolean isInstalled(Platform platform, String name) {
|
||||
@@ -74,7 +76,7 @@ public final class HMCLJavaRepository implements JavaRepository {
|
||||
}
|
||||
|
||||
public boolean isInstalled(Platform platform, GameJavaVersion gameJavaVersion) {
|
||||
return isInstalled(platform, MOJANG_JAVA_PREFIX + gameJavaVersion.getComponent());
|
||||
return isInstalled(platform, MOJANG_JAVA_PREFIX + gameJavaVersion.component());
|
||||
}
|
||||
|
||||
public @Nullable Path getJavaExecutable(Platform platform, String name) {
|
||||
@@ -94,7 +96,7 @@ public final class HMCLJavaRepository implements JavaRepository {
|
||||
}
|
||||
|
||||
public @Nullable Path getJavaExecutable(Platform platform, GameJavaVersion gameJavaVersion) {
|
||||
return getJavaExecutable(platform, MOJANG_JAVA_PREFIX + gameJavaVersion.getComponent());
|
||||
return getJavaExecutable(platform, MOJANG_JAVA_PREFIX + gameJavaVersion.component());
|
||||
}
|
||||
|
||||
private static void getAllJava(List<JavaRuntime> list, Platform platform, Path platformRoot, boolean isManaged) {
|
||||
@@ -169,7 +171,7 @@ public final class HMCLJavaRepository implements JavaRepository {
|
||||
|
||||
Map<String, Object> update = new LinkedHashMap<>();
|
||||
update.put("provider", "mojang");
|
||||
update.put("component", gameJavaVersion.getComponent());
|
||||
update.put("component", gameJavaVersion.component());
|
||||
|
||||
Map<String, JavaLocalFiles.Local> files = new LinkedHashMap<>();
|
||||
result.remoteFiles.getFiles().forEach((path, file) -> {
|
||||
|
||||
@@ -36,7 +36,9 @@ public enum VersionIconType {
|
||||
FURNACE("/assets/img/furnace.png"),
|
||||
QUILT("/assets/img/quilt.png"),
|
||||
APRIL_FOOLS("/assets/img/april_fools.png"),
|
||||
CLEANROOM("/assets/img/cleanroom.png");
|
||||
CLEANROOM("/assets/img/cleanroom.png"),
|
||||
LEGACY_FABRIC("/assets/img/legacyfabric.png")
|
||||
;
|
||||
|
||||
// Please append new items at last
|
||||
|
||||
|
||||
@@ -299,6 +299,14 @@ public final class FXUtils {
|
||||
});
|
||||
}
|
||||
|
||||
public static Node wrap(Node node) {
|
||||
return limitingSize(node, 30, 20);
|
||||
}
|
||||
|
||||
public static Node wrap(SVG svg) {
|
||||
return wrap(svg.createIcon(20));
|
||||
}
|
||||
|
||||
private static class ListenerPair<T> {
|
||||
private final ObservableValue<T> value;
|
||||
private final ChangeListener<? super T> listener;
|
||||
@@ -1397,6 +1405,15 @@ public final class FXUtils {
|
||||
});
|
||||
}
|
||||
|
||||
public static void onSecondaryButtonClicked(Node node, Runnable action) {
|
||||
node.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> {
|
||||
if (e.getButton() == MouseButton.SECONDARY) {
|
||||
action.run();
|
||||
e.consume();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static <N extends Parent> N prepareNode(N node) {
|
||||
Scene dummyScene = new Scene(node);
|
||||
StyleSheets.init(dummyScene);
|
||||
|
||||
@@ -92,37 +92,18 @@ public class InstallerItem extends Control {
|
||||
this.id = id;
|
||||
this.style = style;
|
||||
|
||||
switch (id) {
|
||||
case "game":
|
||||
iconType = VersionIconType.GRASS;
|
||||
break;
|
||||
case "fabric":
|
||||
case "fabric-api":
|
||||
iconType = VersionIconType.FABRIC;
|
||||
break;
|
||||
case "forge":
|
||||
iconType = VersionIconType.FORGE;
|
||||
break;
|
||||
case "cleanroom":
|
||||
iconType = VersionIconType.CLEANROOM;
|
||||
break;
|
||||
case "liteloader":
|
||||
iconType = VersionIconType.CHICKEN;
|
||||
break;
|
||||
case "optifine":
|
||||
iconType = VersionIconType.OPTIFINE;
|
||||
break;
|
||||
case "quilt":
|
||||
case "quilt-api":
|
||||
iconType = VersionIconType.QUILT;
|
||||
break;
|
||||
case "neoforge":
|
||||
iconType = VersionIconType.NEO_FORGE;
|
||||
break;
|
||||
default:
|
||||
iconType = null;
|
||||
break;
|
||||
}
|
||||
iconType = switch (id) {
|
||||
case "game" -> VersionIconType.GRASS;
|
||||
case "fabric", "fabric-api" -> VersionIconType.FABRIC;
|
||||
case "legacyfabric", "legacyfabric-api" -> VersionIconType.LEGACY_FABRIC;
|
||||
case "forge" -> VersionIconType.FORGE;
|
||||
case "cleanroom" -> VersionIconType.CLEANROOM;
|
||||
case "liteloader" -> VersionIconType.CHICKEN;
|
||||
case "optifine" -> VersionIconType.OPTIFINE;
|
||||
case "quilt", "quilt-api" -> VersionIconType.QUILT;
|
||||
case "neoforge" -> VersionIconType.NEO_FORGE;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
public String getLibraryId() {
|
||||
@@ -201,6 +182,8 @@ public class InstallerItem extends Control {
|
||||
InstallerItem fabricApi = new InstallerItem(FABRIC_API, style);
|
||||
InstallerItem forge = new InstallerItem(FORGE, style);
|
||||
InstallerItem cleanroom = new InstallerItem(CLEANROOM, style);
|
||||
InstallerItem legacyfabric = new InstallerItem(LEGACY_FABRIC, style);
|
||||
InstallerItem legacyfabricApi = new InstallerItem(LEGACY_FABRIC_API, style);
|
||||
InstallerItem neoForge = new InstallerItem(NEO_FORGE, style);
|
||||
InstallerItem liteLoader = new InstallerItem(LITELOADER, style);
|
||||
InstallerItem optiFine = new InstallerItem(OPTIFINE, style);
|
||||
@@ -208,11 +191,11 @@ public class InstallerItem extends Control {
|
||||
InstallerItem quiltApi = new InstallerItem(QUILT_API, style);
|
||||
|
||||
Map<InstallerItem, Set<InstallerItem>> incompatibleMap = new HashMap<>();
|
||||
mutualIncompatible(incompatibleMap, forge, fabric, quilt, neoForge, cleanroom);
|
||||
addIncompatibles(incompatibleMap, liteLoader, fabric, quilt, neoForge, cleanroom);
|
||||
addIncompatibles(incompatibleMap, optiFine, fabric, quilt, neoForge, cleanroom);
|
||||
addIncompatibles(incompatibleMap, fabricApi, forge, quiltApi, neoForge, liteLoader, optiFine, cleanroom);
|
||||
addIncompatibles(incompatibleMap, quiltApi, forge, fabric, fabricApi, neoForge, liteLoader, optiFine, cleanroom);
|
||||
mutualIncompatible(incompatibleMap, forge, fabric, quilt, neoForge, cleanroom, legacyfabric);
|
||||
addIncompatibles(incompatibleMap, liteLoader, fabric, quilt, neoForge, cleanroom, legacyfabric);
|
||||
addIncompatibles(incompatibleMap, optiFine, fabric, quilt, neoForge, cleanroom, liteLoader, legacyfabric);
|
||||
addIncompatibles(incompatibleMap, fabricApi, forge, quiltApi, neoForge, liteLoader, optiFine, cleanroom, legacyfabricApi, legacyfabricApi);
|
||||
addIncompatibles(incompatibleMap, quiltApi, forge, fabric, fabricApi, neoForge, liteLoader, optiFine, cleanroom, legacyfabric, legacyfabricApi);
|
||||
|
||||
for (Map.Entry<InstallerItem, Set<InstallerItem>> entry : incompatibleMap.entrySet()) {
|
||||
InstallerItem item = entry.getKey();
|
||||
@@ -246,7 +229,7 @@ public class InstallerItem extends Control {
|
||||
game.versionProperty.set(new InstalledState(gameVersion, false, false));
|
||||
}
|
||||
|
||||
InstallerItem[] all = {game, forge, neoForge, liteLoader, optiFine, fabric, fabricApi, quilt, quiltApi, cleanroom};
|
||||
InstallerItem[] all = {game, forge, neoForge, liteLoader, optiFine, fabric, fabricApi, quilt, quiltApi, legacyfabric, legacyfabricApi, cleanroom};
|
||||
|
||||
for (InstallerItem item : all) {
|
||||
if (!item.resolvedStateProperty.isBound()) {
|
||||
@@ -263,9 +246,9 @@ public class InstallerItem extends Control {
|
||||
if (gameVersion == null) {
|
||||
this.libraries = all;
|
||||
} else if (gameVersion.equals("1.12.2")) {
|
||||
this.libraries = new InstallerItem[]{game, forge, cleanroom, liteLoader, optiFine};
|
||||
} else if (GameVersionNumber.compare(gameVersion, "1.13") < 0) {
|
||||
this.libraries = new InstallerItem[]{game, forge, liteLoader, optiFine};
|
||||
this.libraries = new InstallerItem[]{game, forge, cleanroom, liteLoader, legacyfabric, legacyfabricApi, optiFine};
|
||||
} else if (GameVersionNumber.compare(gameVersion, "1.13.2") <= 0) {
|
||||
this.libraries = new InstallerItem[]{game, forge, liteLoader, optiFine, legacyfabric, legacyfabricApi};
|
||||
} else {
|
||||
this.libraries = new InstallerItem[]{game, forge, neoForge, optiFine, fabric, fabricApi, quilt, quiltApi};
|
||||
}
|
||||
@@ -300,11 +283,15 @@ public class InstallerItem extends Control {
|
||||
}
|
||||
pane.getStyleClass().add("installer-item");
|
||||
RipplerContainer container = new RipplerContainer(pane);
|
||||
container.setPosition(JFXRippler.RipplerPos.FRONT);
|
||||
getChildren().setAll(container);
|
||||
container.setPosition(JFXRippler.RipplerPos.BACK);
|
||||
StackPane paneWrapper = new StackPane();
|
||||
paneWrapper.getStyleClass().add("installer-item-wrapper");
|
||||
paneWrapper.getChildren().setAll(container);
|
||||
getChildren().setAll(paneWrapper);
|
||||
|
||||
pane.pseudoClassStateChanged(LIST_ITEM, control.style == Style.LIST_ITEM);
|
||||
pane.pseudoClassStateChanged(CARD, control.style == Style.CARD);
|
||||
paneWrapper.pseudoClassStateChanged(CARD, control.style == Style.CARD);
|
||||
|
||||
if (control.iconType != null) {
|
||||
ImageView view = new ImageView(control.iconType.getIcon());
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 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;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Skin;
|
||||
|
||||
public abstract class ListPage<T extends Node> extends ListPageBase<T> {
|
||||
private final BooleanProperty refreshable = new SimpleBooleanProperty(this, "refreshable", false);
|
||||
|
||||
public abstract void add();
|
||||
|
||||
public void refresh() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new ListPageSkin(this);
|
||||
}
|
||||
|
||||
public boolean isRefreshable() {
|
||||
return refreshable.get();
|
||||
}
|
||||
|
||||
public BooleanProperty refreshableProperty() {
|
||||
return refreshable;
|
||||
}
|
||||
|
||||
public void setRefreshable(boolean refreshable) {
|
||||
this.refreshable.set(refreshable);
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 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;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
|
||||
public class ListPageSkin extends SkinBase<ListPage<?>> {
|
||||
|
||||
public ListPageSkin(ListPage<?> skinnable) {
|
||||
super(skinnable);
|
||||
|
||||
SpinnerPane spinnerPane = new SpinnerPane();
|
||||
spinnerPane.getStyleClass().add("large-spinner-pane");
|
||||
Pane placeholder = new Pane();
|
||||
VBox list = new VBox();
|
||||
|
||||
StackPane contentPane = new StackPane();
|
||||
{
|
||||
ScrollPane scrollPane = new ScrollPane();
|
||||
{
|
||||
scrollPane.setFitToWidth(true);
|
||||
|
||||
list.maxWidthProperty().bind(scrollPane.widthProperty());
|
||||
list.setSpacing(10);
|
||||
|
||||
VBox content = new VBox();
|
||||
content.getChildren().setAll(list, placeholder);
|
||||
|
||||
Bindings.bindContent(list.getChildren(), skinnable.itemsProperty());
|
||||
|
||||
scrollPane.setContent(content);
|
||||
FXUtils.smoothScrolling(scrollPane);
|
||||
}
|
||||
|
||||
VBox vBox = new VBox();
|
||||
{
|
||||
vBox.getStyleClass().add("card-list");
|
||||
vBox.setAlignment(Pos.BOTTOM_RIGHT);
|
||||
vBox.setPickOnBounds(false);
|
||||
|
||||
JFXButton btnAdd = FXUtils.newRaisedButton("");
|
||||
FXUtils.setLimitWidth(btnAdd, 40);
|
||||
FXUtils.setLimitHeight(btnAdd, 40);
|
||||
btnAdd.setGraphic(SVG.ADD.createIcon());
|
||||
btnAdd.setOnAction(e -> skinnable.add());
|
||||
|
||||
JFXButton btnRefresh = new JFXButton();
|
||||
FXUtils.setLimitWidth(btnRefresh, 40);
|
||||
FXUtils.setLimitHeight(btnRefresh, 40);
|
||||
btnRefresh.getStyleClass().add("jfx-button-raised-round");
|
||||
btnRefresh.setButtonType(JFXButton.ButtonType.RAISED);
|
||||
btnRefresh.setGraphic(SVG.REFRESH.createIcon());
|
||||
btnRefresh.setOnAction(e -> skinnable.refresh());
|
||||
|
||||
vBox.getChildren().setAll(btnAdd);
|
||||
|
||||
FXUtils.onChangeAndOperate(skinnable.refreshableProperty(),
|
||||
refreshable -> {
|
||||
if (refreshable) {
|
||||
list.setPadding(new Insets(10, 10, 15 + 40 + 15 + 40 + 15, 10));
|
||||
vBox.getChildren().setAll(btnRefresh, btnAdd);
|
||||
} else {
|
||||
list.setPadding(new Insets(10, 10, 15 + 40 + 15, 10));
|
||||
vBox.getChildren().setAll(btnAdd);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Keep a blank space to prevent buttons from blocking up mod items.
|
||||
BorderPane group = new BorderPane();
|
||||
group.setPickOnBounds(false);
|
||||
group.setBottom(vBox);
|
||||
placeholder.minHeightProperty().bind(vBox.heightProperty());
|
||||
|
||||
contentPane.getChildren().setAll(scrollPane, group);
|
||||
}
|
||||
|
||||
spinnerPane.loadingProperty().bind(skinnable.loadingProperty());
|
||||
spinnerPane.setContent(contentPane);
|
||||
|
||||
getChildren().setAll(spinnerPane);
|
||||
}
|
||||
}
|
||||
@@ -54,7 +54,7 @@ import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig;
|
||||
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.wrap;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.createSelectedItemPropertyFor;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
@@ -30,7 +30,6 @@ import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.versions.VersionPage;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -75,7 +74,7 @@ public class AdvancedListBox extends ScrollPane {
|
||||
item.setActionButtonVisible(false);
|
||||
item.setTitle(title);
|
||||
if (leftGraphic != null) {
|
||||
item.setLeftGraphic(VersionPage.wrap(leftGraphic));
|
||||
item.setLeftGraphic(FXUtils.wrap(leftGraphic));
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ import org.jackhuang.hmcl.download.forge.ForgeOldInstallTask;
|
||||
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
|
||||
import org.jackhuang.hmcl.download.game.GameInstallTask;
|
||||
import org.jackhuang.hmcl.download.java.mojang.MojangJavaDownloadTask;
|
||||
import org.jackhuang.hmcl.download.legacyfabric.LegacyFabricInstallTask;
|
||||
import org.jackhuang.hmcl.download.liteloader.LiteLoaderInstallTask;
|
||||
import org.jackhuang.hmcl.download.neoforge.NeoForgeInstallTask;
|
||||
import org.jackhuang.hmcl.download.neoforge.NeoForgeOldInstallTask;
|
||||
@@ -164,6 +165,8 @@ public final class TaskListPane extends StackPane {
|
||||
task.setName(i18n("install.installer.install", i18n("install.installer.game")));
|
||||
} else if (task instanceof CleanroomInstallTask) {
|
||||
task.setName(i18n("install.installer.install", i18n("install.installer.cleanroom")));
|
||||
} else if (task instanceof LegacyFabricInstallTask) {
|
||||
task.setName(i18n("install.installer.install", i18n("install.installer.legacyfabric")));
|
||||
} else if (task instanceof ForgeNewInstallTask || task instanceof ForgeOldInstallTask) {
|
||||
task.setName(i18n("install.installer.install", i18n("install.installer.forge")));
|
||||
} else if (task instanceof NeoForgeInstallTask || task instanceof NeoForgeOldInstallTask) {
|
||||
@@ -427,23 +430,25 @@ public final class TaskListPane extends StackPane {
|
||||
|
||||
// CHECKSTYLE:OFF
|
||||
// @formatter:off
|
||||
switch (stageKey) {
|
||||
case "hmcl.modpack": message = i18n("install.modpack"); break;
|
||||
case "hmcl.modpack.download": message = i18n("launch.state.modpack"); break;
|
||||
case "hmcl.install.assets": message = i18n("assets.download"); break;
|
||||
case "hmcl.install.libraries": message = i18n("libraries.download"); break;
|
||||
case "hmcl.install.game": message = i18n("install.installer.install", i18n("install.installer.game") + " " + stageValue); break;
|
||||
case "hmcl.install.forge": message = i18n("install.installer.install", i18n("install.installer.forge") + " " + stageValue); break;
|
||||
case "hmcl.install.cleanroom": message = i18n("install.installer.install", i18n("install.installer.cleanroom") + " " + stageValue); break;
|
||||
case "hmcl.install.neoforge": message = i18n("install.installer.install", i18n("install.installer.neoforge") + " " + stageValue); break;
|
||||
case "hmcl.install.liteloader": message = i18n("install.installer.install", i18n("install.installer.liteloader") + " " + stageValue); break;
|
||||
case "hmcl.install.optifine": message = i18n("install.installer.install", i18n("install.installer.optifine") + " " + stageValue); break;
|
||||
case "hmcl.install.fabric": message = i18n("install.installer.install", i18n("install.installer.fabric") + " " + stageValue); break;
|
||||
case "hmcl.install.fabric-api": message = i18n("install.installer.install", i18n("install.installer.fabric-api") + " " + stageValue); break;
|
||||
case "hmcl.install.quilt": message = i18n("install.installer.install", i18n("install.installer.quilt") + " " + stageValue); break;
|
||||
case "hmcl.install.quilt-api": message = i18n("install.installer.install", i18n("install.installer.quilt-api") + " " + stageValue); break;
|
||||
default: message = i18n(stageKey); break;
|
||||
}
|
||||
message = switch (stageKey) {
|
||||
case "hmcl.modpack" -> i18n("install.modpack");
|
||||
case "hmcl.modpack.download" -> i18n("launch.state.modpack");
|
||||
case "hmcl.install.assets" -> i18n("assets.download");
|
||||
case "hmcl.install.libraries" -> i18n("libraries.download");
|
||||
case "hmcl.install.game" -> i18n("install.installer.install", i18n("install.installer.game") + " " + stageValue);
|
||||
case "hmcl.install.forge" -> i18n("install.installer.install", i18n("install.installer.forge") + " " + stageValue);
|
||||
case "hmcl.install.cleanroom" -> i18n("install.installer.install", i18n("install.installer.cleanroom") + " " + stageValue);
|
||||
case "hmcl.install.neoforge" -> i18n("install.installer.install", i18n("install.installer.neoforge") + " " + stageValue);
|
||||
case "hmcl.install.liteloader" -> i18n("install.installer.install", i18n("install.installer.liteloader") + " " + stageValue);
|
||||
case "hmcl.install.optifine" -> i18n("install.installer.install", i18n("install.installer.optifine") + " " + stageValue);
|
||||
case "hmcl.install.fabric" -> i18n("install.installer.install", i18n("install.installer.fabric") + " " + stageValue);
|
||||
case "hmcl.install.fabric-api" -> i18n("install.installer.install", i18n("install.installer.fabric-api") + " " + stageValue);
|
||||
case "hmcl.install.legacyfabric" -> i18n("install.installer.install", i18n("install.installer.legacyfabric") + " " + stageValue);
|
||||
case "hmcl.install.legacyfabric-api" -> i18n("install.installer.install", i18n("install.installer.legacyfabric-api") + " " + stageValue);
|
||||
case "hmcl.install.quilt" -> i18n("install.installer.install", i18n("install.installer.quilt") + " " + stageValue);
|
||||
case "hmcl.install.quilt-api" -> i18n("install.installer.install", i18n("install.installer.quilt-api") + " " + stageValue);
|
||||
default -> i18n(stageKey);
|
||||
};
|
||||
// @formatter:on
|
||||
// CHECKSTYLE:ON
|
||||
|
||||
|
||||
@@ -264,7 +264,7 @@ public class DecoratorController {
|
||||
private Background createBackgroundWithOpacity(Image image, int opacity) {
|
||||
if (opacity <= 0) {
|
||||
return new Background(new BackgroundFill(new Color(1, 1, 1, 0), CornerRadii.EMPTY, Insets.EMPTY));
|
||||
} else if (opacity >= 100) {
|
||||
} else if (opacity >= 100 || image.getPixelReader() == null) {
|
||||
return new Background(new BackgroundImage(
|
||||
image,
|
||||
BackgroundRepeat.NO_REPEAT,
|
||||
|
||||
@@ -328,6 +328,18 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
}
|
||||
if (onTitleBarDoubleClick != null)
|
||||
center.setOnMouseClicked(onTitleBarDoubleClick);
|
||||
center.setOnMouseDragged(mouseEvent -> {
|
||||
if (!getSkinnable().isDragging() && primaryStage.isMaximized()) {
|
||||
getSkinnable().setDragging(true);
|
||||
mouseInitX = mouseEvent.getScreenX();
|
||||
mouseInitY = mouseEvent.getScreenY();
|
||||
primaryStage.setMaximized(false);
|
||||
stageInitWidth = primaryStage.getWidth();
|
||||
stageInitHeight = primaryStage.getHeight();
|
||||
primaryStage.setY(stageInitY = 0);
|
||||
primaryStage.setX(stageInitX = mouseInitX - stageInitWidth / 2);
|
||||
}
|
||||
});
|
||||
navBar.setCenter(center);
|
||||
|
||||
if (canRefresh) {
|
||||
|
||||
@@ -24,12 +24,12 @@ import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.InstallerItem;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
@@ -38,9 +38,11 @@ import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.SettingsMap;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public abstract class AbstractInstallersPage extends Control implements WizardPage {
|
||||
public static final String FABRIC_QUILT_API_TIP = "fabricQuiltApi";
|
||||
protected final WizardController controller;
|
||||
|
||||
protected InstallerItem.InstallerItemGroup group;
|
||||
@@ -56,8 +58,15 @@ public abstract class AbstractInstallersPage extends Control implements WizardPa
|
||||
String libraryId = library.getLibraryId();
|
||||
if (libraryId.equals(LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId())) continue;
|
||||
library.setOnInstall(() -> {
|
||||
if (LibraryAnalyzer.LibraryType.FABRIC_API.getPatchId().equals(libraryId)) {
|
||||
Controllers.dialog(i18n("install.installer.fabric-api.warning"), i18n("message.warning"), MessageDialogPane.MessageType.WARNING);
|
||||
if (!Boolean.TRUE.equals(config().getShownTips().get(FABRIC_QUILT_API_TIP))
|
||||
&& (LibraryAnalyzer.LibraryType.FABRIC_API.getPatchId().equals(libraryId)
|
||||
|| LibraryAnalyzer.LibraryType.QUILT_API.getPatchId().equals(libraryId)
|
||||
|| LibraryAnalyzer.LibraryType.LEGACY_FABRIC_API.getPatchId().equals(libraryId))) {
|
||||
Controllers.dialog(new MessageDialogPane.Builder(
|
||||
i18n("install.installer.fabric-quilt-api.warning", i18n("install.installer." + libraryId)),
|
||||
i18n("message.warning"),
|
||||
MessageDialogPane.MessageType.WARNING
|
||||
).ok(null).addCancel(i18n("button.do_not_show_again"), () -> config().getShownTips().put(FABRIC_QUILT_API_TIP, true)).build());
|
||||
}
|
||||
|
||||
if (!(library.resolvedStateProperty().get() instanceof InstallerItem.IncompatibleState))
|
||||
|
||||
@@ -121,32 +121,20 @@ public class InstallersPage extends AbstractInstallersPage {
|
||||
|
||||
LibraryAnalyzer.LibraryType libraryType = LibraryAnalyzer.LibraryType.fromPatchId(libraryId);
|
||||
if (libraryType != null) {
|
||||
String loaderName;
|
||||
switch (libraryType) {
|
||||
case FORGE:
|
||||
loaderName = i18n("install.installer.forge");
|
||||
break;
|
||||
case NEO_FORGE:
|
||||
loaderName = i18n("install.installer.neoforge");
|
||||
break;
|
||||
case CLEANROOM:
|
||||
loaderName = i18n("install.installer.cleanroom");
|
||||
break;
|
||||
case FABRIC:
|
||||
loaderName = i18n("install.installer.fabric");
|
||||
break;
|
||||
case LITELOADER:
|
||||
loaderName = i18n("install.installer.liteloader");
|
||||
break;
|
||||
case QUILT:
|
||||
loaderName = i18n("install.installer.quilt");
|
||||
break;
|
||||
case OPTIFINE:
|
||||
loaderName = i18n("install.installer.optifine");
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
String loaderName = switch (libraryType) {
|
||||
case FORGE -> i18n("install.installer.forge");
|
||||
case NEO_FORGE -> i18n("install.installer.neoforge");
|
||||
case CLEANROOM -> i18n("install.installer.cleanroom");
|
||||
case LEGACY_FABRIC -> i18n("install.installer.legacyfabric").replace(" ", "_");
|
||||
case FABRIC -> i18n("install.installer.fabric");
|
||||
case LITELOADER -> i18n("install.installer.liteloader");
|
||||
case QUILT -> i18n("install.installer.quilt");
|
||||
case OPTIFINE -> i18n("install.installer.optifine");
|
||||
default -> null;
|
||||
};
|
||||
|
||||
if (loaderName == null)
|
||||
continue;
|
||||
|
||||
nameBuilder.append("-").append(loaderName);
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ import org.jackhuang.hmcl.download.fabric.FabricAPIRemoteVersion;
|
||||
import org.jackhuang.hmcl.download.fabric.FabricRemoteVersion;
|
||||
import org.jackhuang.hmcl.download.forge.ForgeRemoteVersion;
|
||||
import org.jackhuang.hmcl.download.game.GameRemoteVersion;
|
||||
import org.jackhuang.hmcl.download.legacyfabric.LegacyFabricAPIRemoteVersion;
|
||||
import org.jackhuang.hmcl.download.legacyfabric.LegacyFabricRemoteVersion;
|
||||
import org.jackhuang.hmcl.download.liteloader.LiteLoaderRemoteVersion;
|
||||
import org.jackhuang.hmcl.download.neoforge.NeoForgeRemoteVersion;
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineRemoteVersion;
|
||||
@@ -270,6 +272,8 @@ public final class VersionsPage extends Control implements WizardPage, Refreshab
|
||||
iconType = VersionIconType.CLEANROOM;
|
||||
else if (remoteVersion instanceof NeoForgeRemoteVersion)
|
||||
iconType = VersionIconType.NEO_FORGE;
|
||||
else if (remoteVersion instanceof LegacyFabricRemoteVersion || remoteVersion instanceof LegacyFabricAPIRemoteVersion)
|
||||
iconType = VersionIconType.LEGACY_FABRIC;
|
||||
else if (remoteVersion instanceof FabricRemoteVersion || remoteVersion instanceof FabricAPIRemoteVersion)
|
||||
iconType = VersionIconType.FABRIC;
|
||||
else if (remoteVersion instanceof QuiltRemoteVersion || remoteVersion instanceof QuiltAPIRemoteVersion)
|
||||
|
||||
@@ -22,10 +22,10 @@ import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.mod.ModAdviser;
|
||||
import org.jackhuang.hmcl.mod.ModpackExportInfo;
|
||||
import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackExportTask;
|
||||
import org.jackhuang.hmcl.mod.modrinth.ModrinthModpackExportTask;
|
||||
import org.jackhuang.hmcl.mod.multimc.MultiMCInstanceConfiguration;
|
||||
import org.jackhuang.hmcl.mod.multimc.MultiMCModpackExportTask;
|
||||
import org.jackhuang.hmcl.mod.server.ServerModpackExportTask;
|
||||
import org.jackhuang.hmcl.mod.modrinth.ModrinthModpackExportTask;
|
||||
import org.jackhuang.hmcl.setting.Config;
|
||||
import org.jackhuang.hmcl.setting.FontManager;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
@@ -41,7 +41,9 @@ import org.jackhuang.hmcl.util.io.Zipper;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
@@ -132,7 +134,9 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
exported.getAuthlibInjectorServers().setAll(config().getAuthlibInjectorServers());
|
||||
|
||||
zip.putTextFile(exported.toJson(), ".hmcl/hmcl.json");
|
||||
zip.putFile(tempModpack, "modpack.zip");
|
||||
zip.putFile(tempModpack, ModpackTypeSelectionPage.MODPACK_TYPE_MODRINTH.equals(modpackType)
|
||||
? "modpack.mrpack"
|
||||
: "modpack.zip");
|
||||
|
||||
Path bg = Metadata.HMCL_CURRENT_DIRECTORY.resolve("background");
|
||||
if (!Files.isDirectory(bg))
|
||||
@@ -263,10 +267,10 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
@Override
|
||||
public void execute() {
|
||||
dependency = new ModrinthModpackExportTask(
|
||||
profile.getRepository(),
|
||||
version,
|
||||
exportInfo,
|
||||
modpackFile
|
||||
profile.getRepository(),
|
||||
version,
|
||||
exportInfo,
|
||||
modpackFile
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ public final class ModpackInfoPage extends Control implements WizardPage {
|
||||
private void onNext() {
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle(i18n("modpack.wizard.step.initialization.save"));
|
||||
if (controller.getSettings().get(MODPACK_TYPE) == MODPACK_TYPE_MODRINTH) {
|
||||
if (!packWithLauncher.get() && controller.getSettings().get(MODPACK_TYPE) == MODPACK_TYPE_MODRINTH) {
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("modpack"), "*.mrpack"));
|
||||
fileChooser.setInitialFileName(name.get() + (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS ? "" : ".mrpack"));
|
||||
} else {
|
||||
|
||||
@@ -38,7 +38,10 @@ import org.jackhuang.hmcl.game.GameJavaVersion;
|
||||
import org.jackhuang.hmcl.java.JavaInfo;
|
||||
import org.jackhuang.hmcl.java.JavaManager;
|
||||
import org.jackhuang.hmcl.setting.DownloadProviders;
|
||||
import org.jackhuang.hmcl.task.*;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
@@ -116,7 +119,7 @@ public final class JavaDownloadDialog extends StackPane {
|
||||
vbox.getChildren().add(prompt);
|
||||
|
||||
for (GameJavaVersion version : supportedGameJavaVersions) {
|
||||
JFXRadioButton button = new JFXRadioButton("Java " + version.getMajorVersion());
|
||||
JFXRadioButton button = new JFXRadioButton("Java " + version.majorVersion());
|
||||
button.setUserData(version);
|
||||
vbox.getChildren().add(button);
|
||||
toggleGroup.getToggles().add(button);
|
||||
@@ -248,7 +251,7 @@ public final class JavaDownloadDialog extends StackPane {
|
||||
|
||||
for (int i = 0; i < versions.size(); i++) {
|
||||
DiscoJavaRemoteVersion version = versions.get(i);
|
||||
if (version.getJdkVersion() == GameJavaVersion.LATEST.getMajorVersion()) {
|
||||
if (version.getJdkVersion() == GameJavaVersion.LATEST.majorVersion()) {
|
||||
remoteVersionBox.getSelectionModel().select(i);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -59,7 +59,6 @@ import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.ui.versions.GameItem;
|
||||
import org.jackhuang.hmcl.ui.versions.GameListPopupMenu;
|
||||
import org.jackhuang.hmcl.ui.versions.Versions;
|
||||
import org.jackhuang.hmcl.upgrade.RemoteVersion;
|
||||
@@ -246,7 +245,14 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
|
||||
menuButton = new JFXButton();
|
||||
menuButton.getStyleClass().add("menu-button");
|
||||
menuButton.setOnAction(e -> onMenu());
|
||||
menuButton.setOnAction(e -> GameListPopupMenu.show(
|
||||
menuButton,
|
||||
JFXPopup.PopupVPosition.BOTTOM,
|
||||
JFXPopup.PopupHPosition.RIGHT,
|
||||
0,
|
||||
-menuButton.getHeight(),
|
||||
profile, versions
|
||||
));
|
||||
FXUtils.installFastTooltip(menuButton, i18n("version.switch"));
|
||||
menuButton.setGraphic(SVG.ARROW_DROP_UP.createIcon(30));
|
||||
|
||||
@@ -342,19 +348,6 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
Controllers.taskDialog(task, i18n("version.launch.empty.installing"), TaskCancellationAction.NORMAL);
|
||||
}
|
||||
|
||||
private void onMenu() {
|
||||
GameListPopupMenu menu = new GameListPopupMenu();
|
||||
menu.getItems().setAll(versions.stream().map(it -> new GameItem(profile, it.getId())).toList());
|
||||
JFXPopup popup = new JFXPopup(menu);
|
||||
popup.show(
|
||||
menuButton,
|
||||
JFXPopup.PopupVPosition.BOTTOM,
|
||||
JFXPopup.PopupHPosition.RIGHT,
|
||||
0,
|
||||
-menuButton.getHeight()
|
||||
);
|
||||
}
|
||||
|
||||
private void onUpgrade() {
|
||||
RemoteVersion target = UpdateChecker.getLatestVersion();
|
||||
if (target == null) {
|
||||
|
||||
@@ -20,7 +20,6 @@ package org.jackhuang.hmcl.ui.main;
|
||||
import com.jfoenix.controls.JFXPopup;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
@@ -49,6 +48,7 @@ import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.nbt.NBTEditorPage;
|
||||
import org.jackhuang.hmcl.ui.nbt.NBTFileType;
|
||||
import org.jackhuang.hmcl.ui.versions.GameAdvancedListItem;
|
||||
import org.jackhuang.hmcl.ui.versions.GameListPopupMenu;
|
||||
import org.jackhuang.hmcl.ui.versions.Versions;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
@@ -67,7 +67,7 @@ import java.util.Locale;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.wrap;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
@@ -148,12 +148,7 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
// first item in left sidebar
|
||||
AccountAdvancedListItem accountListItem = new AccountAdvancedListItem();
|
||||
accountListItem.setOnAction(e -> Controllers.navigate(Controllers.getAccountListPage()));
|
||||
accountListItem.setOnMouseClicked(e -> {
|
||||
if (e.getButton() == MouseButton.SECONDARY) {
|
||||
showAccountListPopupMenu(accountListItem);
|
||||
e.consume();
|
||||
}
|
||||
});
|
||||
FXUtils.onSecondaryButtonClicked(accountListItem, () -> showAccountListPopupMenu(accountListItem));
|
||||
accountListItem.accountProperty().bind(Accounts.selectedAccountProperty());
|
||||
|
||||
// second item in left sidebar
|
||||
@@ -174,6 +169,7 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
if (AnimationUtils.isAnimationEnabled()) {
|
||||
FXUtils.prepareOnMouseEnter(gameListItem, Controllers::prepareVersionPage);
|
||||
}
|
||||
FXUtils.onSecondaryButtonClicked(gameListItem, () -> showGameListPopupMenu(gameListItem));
|
||||
|
||||
// third item in left sidebar
|
||||
AdvancedListItem gameItem = new AdvancedListItem();
|
||||
@@ -181,6 +177,7 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
gameItem.setActionButtonVisible(false);
|
||||
gameItem.setTitle(i18n("version.manage"));
|
||||
gameItem.setOnAction(e -> Controllers.navigate(Controllers.getGameListPage()));
|
||||
FXUtils.onSecondaryButtonClicked(gameItem, () -> showGameListPopupMenu(gameItem));
|
||||
|
||||
// forth item in left sidebar
|
||||
AdvancedListItem downloadItem = new AdvancedListItem();
|
||||
@@ -283,6 +280,16 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
popupMenu.getContent().add(scrollPane);
|
||||
popup.show(accountListItem, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, accountListItem.getWidth(), 0);
|
||||
}
|
||||
|
||||
public void showGameListPopupMenu(AdvancedListItem gameListItem) {
|
||||
GameListPopupMenu.show(gameListItem,
|
||||
JFXPopup.PopupVPosition.TOP,
|
||||
JFXPopup.PopupHPosition.LEFT,
|
||||
gameListItem.getWidth(),
|
||||
0,
|
||||
getSkinnable().getMainPage().getProfile(),
|
||||
getSkinnable().getMainPage().getVersions());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkedModpack = false;
|
||||
@@ -293,11 +300,21 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
checkedModpack = true;
|
||||
|
||||
if (repository.getVersionCount() == 0) {
|
||||
Path modpackFile = Metadata.CURRENT_DIRECTORY.resolve("modpack.zip");
|
||||
if (Files.exists(modpackFile)) {
|
||||
Path zipModpack = Metadata.CURRENT_DIRECTORY.resolve("modpack.zip");
|
||||
Path mrpackModpack = Metadata.CURRENT_DIRECTORY.resolve("modpack.mrpack");
|
||||
|
||||
Path modpackFile;
|
||||
if (Files.exists(zipModpack)) {
|
||||
modpackFile = zipModpack;
|
||||
} else if (Files.exists(mrpackModpack)) {
|
||||
modpackFile = mrpackModpack;
|
||||
} else {
|
||||
modpackFile = null;
|
||||
}
|
||||
|
||||
if (modpackFile != null) {
|
||||
Task.supplyAsync(() -> CompressingUtils.findSuitableEncoding(modpackFile))
|
||||
.thenApplyAsync(
|
||||
encoding -> ModpackHelper.readModpackManifest(modpackFile, encoding))
|
||||
.thenApplyAsync(encoding -> ModpackHelper.readModpackManifest(modpackFile, encoding))
|
||||
.thenApplyAsync(modpack -> ModpackHelper
|
||||
.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack, null)
|
||||
.executor())
|
||||
|
||||
@@ -28,7 +28,6 @@ import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.ui.versions.VersionPage;
|
||||
|
||||
public class ProfileListItemSkin extends SkinBase<ProfileListItem> {
|
||||
private final PseudoClass SELECTED = PseudoClass.getPseudoClass("selected");
|
||||
@@ -47,7 +46,7 @@ public class ProfileListItemSkin extends SkinBase<ProfileListItem> {
|
||||
|
||||
FXUtils.onClicked(getSkinnable(), () -> getSkinnable().setSelected(true));
|
||||
|
||||
Node left = VersionPage.wrap(SVG.FOLDER);
|
||||
Node left = FXUtils.wrap(SVG.FOLDER);
|
||||
root.setLeft(left);
|
||||
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.terracotta;
|
||||
|
||||
import com.jfoenix.controls.JFXPopup;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
@@ -35,6 +36,7 @@ import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.ui.main.MainPage;
|
||||
import org.jackhuang.hmcl.ui.versions.GameListPopupMenu;
|
||||
import org.jackhuang.hmcl.ui.versions.Versions;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
@@ -84,6 +86,13 @@ public class TerracottaPage extends DecoratorAnimatedPage implements DecoratorPa
|
||||
String currentId = mainPage.getCurrentGame();
|
||||
return Lang.indexWhere(list, instance -> instance.getId().equals(currentId));
|
||||
}, it -> mainPage.getProfile().setSelectedVersion(it.getId()));
|
||||
|
||||
FXUtils.onSecondaryButtonClicked(item, () -> GameListPopupMenu.show(item,
|
||||
JFXPopup.PopupVPosition.BOTTOM,
|
||||
JFXPopup.PopupHPosition.LEFT,
|
||||
item.getWidth(),
|
||||
0,
|
||||
mainPage.getProfile(), mainPage.getVersions()));
|
||||
})
|
||||
.addNavigationDrawerItem(i18n("terracotta.feedback.title"), SVG.FEEDBACK, () -> FXUtils.openLink(TerracottaMetadata.FEEDBACK_LINK));
|
||||
BorderPane.setMargin(toolbar, new Insets(0, 0, 12, 0));
|
||||
|
||||
@@ -82,6 +82,7 @@ public final class GameListCell extends ListCell<GameListItem> {
|
||||
{
|
||||
HBox center = new HBox();
|
||||
root.setCenter(center);
|
||||
center.setPrefWidth(Region.USE_PREF_SIZE);
|
||||
center.setSpacing(8);
|
||||
center.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ public class GameListPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
addProfileItem.getStyleClass().add("navigation-drawer-item");
|
||||
addProfileItem.setTitle(i18n("profile.new"));
|
||||
addProfileItem.setActionButtonVisible(false);
|
||||
addProfileItem.setLeftGraphic(VersionPage.wrap(SVG.ADD_CIRCLE));
|
||||
addProfileItem.setLeftGraphic(FXUtils.wrap(SVG.ADD_CIRCLE));
|
||||
addProfileItem.setOnAction(e -> Controllers.navigate(new ProfilePage(null)));
|
||||
|
||||
pane.setFitToWidth(true);
|
||||
|
||||
@@ -26,6 +26,7 @@ import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ListView;
|
||||
@@ -34,15 +35,29 @@ import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
/// @author Glavo
|
||||
public final class GameListPopupMenu extends StackPane {
|
||||
|
||||
public static void show(Node owner, JFXPopup.PopupVPosition vAlign, JFXPopup.PopupHPosition hAlign,
|
||||
double initOffsetX, double initOffsetY,
|
||||
Profile profile, List<Version> versions) {
|
||||
GameListPopupMenu menu = new GameListPopupMenu();
|
||||
menu.getItems().setAll(versions.stream().map(it -> new GameItem(profile, it.getId())).toList());
|
||||
JFXPopup popup = new JFXPopup(menu);
|
||||
popup.show(owner, vAlign, hAlign, initOffsetX, initOffsetY);
|
||||
}
|
||||
|
||||
private final JFXListView<GameItem> listView = new JFXListView<>();
|
||||
private final BooleanBinding isEmpty = Bindings.isEmpty(listView.getItems());
|
||||
|
||||
|
||||
@@ -76,12 +76,12 @@ public class InstallerListPage extends ListPageBase<InstallerItem> implements Ve
|
||||
|
||||
InstallerItem.InstallerItemGroup group = new InstallerItem.InstallerItemGroup(gameVersion, InstallerItem.Style.LIST_ITEM);
|
||||
|
||||
// Conventional libraries: game, fabric, forge, cleanroom, neoforge, liteloader, optifine
|
||||
// Conventional libraries: game, fabric, legacyfabric, forge, cleanroom, neoforge, liteloader, optifine
|
||||
for (InstallerItem item : group.getLibraries()) {
|
||||
String libraryId = item.getLibraryId();
|
||||
|
||||
// Skip fabric-api and quilt-api
|
||||
if (libraryId.contains("fabric-api") || libraryId.contains("quilt-api")) {
|
||||
// Skip fabric-api and quilt-api and legacyfabric-api
|
||||
if (libraryId.endsWith("-api")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,34 +19,45 @@ package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public class ModCheckUpdatesTask extends Task<List<LocalModFile.ModUpdate>> {
|
||||
private final String gameVersion;
|
||||
private final Collection<LocalModFile> mods;
|
||||
private final Collection<Collection<Task<LocalModFile.ModUpdate>>> dependents;
|
||||
private final List<Task<LocalModFile.ModUpdate>> dependents;
|
||||
|
||||
public ModCheckUpdatesTask(String gameVersion, Collection<LocalModFile> mods) {
|
||||
this.gameVersion = gameVersion;
|
||||
this.mods = mods;
|
||||
dependents = mods.stream().map(mod ->
|
||||
Task.supplyAsync(Schedulers.io(), () -> {
|
||||
LocalModFile.ModUpdate candidate = null;
|
||||
for (RemoteMod.Type type : RemoteMod.Type.values()) {
|
||||
LocalModFile.ModUpdate update = null;
|
||||
try {
|
||||
update = mod.checkUpdates(gameVersion, type.getRemoteModRepository());
|
||||
} catch (IOException e) {
|
||||
LOG.warning(String.format("Cannot check update for mod %s.", mod.getFileName()), e);
|
||||
}
|
||||
if (update == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
dependents = mods.stream()
|
||||
.map(mod ->
|
||||
Arrays.stream(RemoteMod.Type.values())
|
||||
.map(type ->
|
||||
Task.supplyAsync(() -> mod.checkUpdates(gameVersion, type.getRemoteModRepository()))
|
||||
.setSignificance(TaskSignificance.MAJOR)
|
||||
.setName(String.format("%s (%s)", mod.getFileName(), type.name())).withCounter("update.checking")
|
||||
)
|
||||
.collect(Collectors.toList())
|
||||
)
|
||||
.collect(Collectors.toList());
|
||||
if (candidate == null || candidate.getCandidate().getDatePublished().isBefore(update.getCandidate().getDatePublished())) {
|
||||
candidate = update;
|
||||
}
|
||||
}
|
||||
|
||||
return candidate;
|
||||
}).setName(mod.getFileName()).setSignificance(TaskSignificance.MAJOR).withCounter("update.checking")
|
||||
).toList();
|
||||
|
||||
setStage("update.checking");
|
||||
getProperties().put("total", dependents.size() * RemoteMod.Type.values().length);
|
||||
getProperties().put("total", dependents.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -61,7 +72,7 @@ public class ModCheckUpdatesTask extends Task<List<LocalModFile.ModUpdate>> {
|
||||
|
||||
@Override
|
||||
public Collection<? extends Task<?>> getDependents() {
|
||||
return dependents.stream().flatMap(Collection::stream).collect(Collectors.toList());
|
||||
return dependents;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -72,14 +83,7 @@ public class ModCheckUpdatesTask extends Task<List<LocalModFile.ModUpdate>> {
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
setResult(dependents.stream()
|
||||
.map(tasks -> tasks.stream()
|
||||
.filter(task -> task.getResult() != null)
|
||||
.map(Task::getResult)
|
||||
.filter(modUpdate -> !modUpdate.getCandidates().isEmpty())
|
||||
.max(Comparator.comparing((LocalModFile.ModUpdate modUpdate) -> modUpdate.getCandidates().get(0).getDatePublished()))
|
||||
.orElse(null)
|
||||
)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList()));
|
||||
.map(Task::getResult)
|
||||
.filter(Objects::nonNull).toList());
|
||||
}
|
||||
}
|
||||
@@ -152,6 +152,10 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
supportedLoaders.add(ModLoaderType.FABRIC);
|
||||
}
|
||||
|
||||
if (analyzer.has(LibraryAnalyzer.LibraryType.LEGACY_FABRIC)) {
|
||||
supportedLoaders.add(ModLoaderType.FABRIC);
|
||||
}
|
||||
|
||||
if (analyzer.has(LibraryAnalyzer.LibraryType.FABRIC) && modManager.hasMod("kilt", ModLoaderType.FABRIC)) {
|
||||
supportedLoaders.add(ModLoaderType.FORGE);
|
||||
supportedLoaders.add(ModLoaderType.NEO_FORGED);
|
||||
|
||||
@@ -470,29 +470,18 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
RemoteMod remoteMod = repository.getModById(versionOptional.get().getModid());
|
||||
FXUtils.runInFX(() -> {
|
||||
for (ModLoaderType modLoaderType : versionOptional.get().getLoaders()) {
|
||||
String loaderName;
|
||||
switch (modLoaderType) {
|
||||
case FORGE:
|
||||
loaderName = i18n("install.installer.forge");
|
||||
break;
|
||||
case CLEANROOM:
|
||||
loaderName = i18n("install.installer.cleanroom");
|
||||
break;
|
||||
case NEO_FORGED:
|
||||
loaderName = i18n("install.installer.neoforge");
|
||||
break;
|
||||
case FABRIC:
|
||||
loaderName = i18n("install.installer.fabric");
|
||||
break;
|
||||
case LITE_LOADER:
|
||||
loaderName = i18n("install.installer.liteloader");
|
||||
break;
|
||||
case QUILT:
|
||||
loaderName = i18n("install.installer.quilt");
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
String loaderName = switch (modLoaderType) {
|
||||
case FORGE -> i18n("install.installer.forge");
|
||||
case CLEANROOM -> i18n("install.installer.cleanroom");
|
||||
case LEGACY_FABRIC -> i18n("install.installer.legacyfabric");
|
||||
case NEO_FORGED -> i18n("install.installer.neoforge");
|
||||
case FABRIC -> i18n("install.installer.fabric");
|
||||
case LITE_LOADER -> i18n("install.installer.liteloader");
|
||||
case QUILT -> i18n("install.installer.quilt");
|
||||
default -> null;
|
||||
};
|
||||
if (loaderName == null)
|
||||
continue;
|
||||
if (title.getTags()
|
||||
.stream()
|
||||
.noneMatch(it -> it.getText().equals(loaderName))) {
|
||||
@@ -664,24 +653,13 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
} else if (!ModListPageSkin.this.getSkinnable().supportedLoaders.contains(modLoaderType)) {
|
||||
warning.add(i18n("mods.warning.loader_mismatch"));
|
||||
switch (dataItem.getModInfo().getModLoaderType()) {
|
||||
case FORGE:
|
||||
content.addTagWarning(i18n("install.installer.forge"));
|
||||
break;
|
||||
case CLEANROOM:
|
||||
content.addTagWarning(i18n("install.installer.cleanroom"));
|
||||
break;
|
||||
case NEO_FORGED:
|
||||
content.addTagWarning(i18n("install.installer.neoforge"));
|
||||
break;
|
||||
case FABRIC:
|
||||
content.addTagWarning(i18n("install.installer.fabric"));
|
||||
break;
|
||||
case LITE_LOADER:
|
||||
content.addTagWarning(i18n("install.installer.liteloader"));
|
||||
break;
|
||||
case QUILT:
|
||||
content.addTagWarning(i18n("install.installer.quilt"));
|
||||
break;
|
||||
case FORGE -> content.addTagWarning(i18n("install.installer.forge"));
|
||||
case LEGACY_FABRIC -> content.addTagWarning(i18n("install.installer.legacyfabric"));
|
||||
case CLEANROOM -> content.addTagWarning(i18n("install.installer.cleanroom"));
|
||||
case NEO_FORGED -> content.addTagWarning(i18n("install.installer.neoforge"));
|
||||
case FABRIC -> content.addTagWarning(i18n("install.installer.fabric"));
|
||||
case LITE_LOADER -> content.addTagWarning(i18n("install.installer.liteloader"));
|
||||
case QUILT -> content.addTagWarning(i18n("install.installer.quilt"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
|
||||
modManager,
|
||||
objects.stream()
|
||||
.filter(o -> o.enabled.get())
|
||||
.map(object -> pair(object.data.getLocalMod(), object.data.getCandidates().get(0)))
|
||||
.map(object -> pair(object.data.getLocalMod(), object.data.getCandidate()))
|
||||
.collect(Collectors.toList()));
|
||||
Controllers.taskDialog(
|
||||
task.whenComplete(Schedulers.javafx(), exception -> {
|
||||
@@ -203,7 +203,7 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
|
||||
enabled.set(!data.getLocalMod().getModManager().isDisabled(data.getLocalMod().getFile()));
|
||||
fileName.set(data.getLocalMod().getFileName());
|
||||
currentVersion.set(data.getCurrentVersion().getVersion());
|
||||
targetVersion.set(data.getCandidates().get(0).getVersion());
|
||||
targetVersion.set(data.getCandidate().getVersion());
|
||||
switch (data.getCurrentVersion().getSelf().getType()) {
|
||||
case CURSEFORGE:
|
||||
source.set(i18n("mods.curseforge"));
|
||||
|
||||
@@ -19,8 +19,10 @@ package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXDialogLayout;
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
@@ -30,6 +32,7 @@ import javafx.scene.image.WritableImage;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.shape.SVGPath;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.schematic.LitematicFile;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
@@ -221,7 +224,7 @@ public final class SchematicsPage extends ListPageBase<SchematicsPage.Item> impl
|
||||
getItems().addAll(item.children);
|
||||
}
|
||||
|
||||
abstract class Item extends Control implements Comparable<Item> {
|
||||
abstract sealed class Item implements Comparable<Item> {
|
||||
|
||||
boolean isDirectory() {
|
||||
return this instanceof DirItem;
|
||||
@@ -258,11 +261,6 @@ public final class SchematicsPage extends ListPageBase<SchematicsPage.Item> impl
|
||||
|
||||
return this.getName().compareTo(o.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new ItemSkin(this);
|
||||
}
|
||||
}
|
||||
|
||||
private final class BackItem extends Item {
|
||||
@@ -438,6 +436,10 @@ public final class SchematicsPage extends ListPageBase<SchematicsPage.Item> impl
|
||||
return SVG.SCHEMA;
|
||||
}
|
||||
|
||||
public @Nullable Image getImage() {
|
||||
return image;
|
||||
}
|
||||
|
||||
Node getIcon(int size) {
|
||||
if (image == null) {
|
||||
return super.getIcon(size);
|
||||
@@ -540,62 +542,122 @@ public final class SchematicsPage extends ListPageBase<SchematicsPage.Item> impl
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ItemSkin extends SkinBase<Item> {
|
||||
public ItemSkin(Item item) {
|
||||
super(item);
|
||||
private static final class Cell extends ListCell<Item> {
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
private final RipplerContainer graphics;
|
||||
private final BorderPane root;
|
||||
private final StackPane left;
|
||||
private final TwoLineListItem center;
|
||||
private final HBox right;
|
||||
|
||||
private final ImageView iconImageView;
|
||||
private final SVGPath iconSVG;
|
||||
private final StackPane iconSVGWrapper;
|
||||
|
||||
private final Tooltip tooltip = new Tooltip();
|
||||
|
||||
public Cell() {
|
||||
this.root = new BorderPane();
|
||||
root.getStyleClass().add("md-list-cell");
|
||||
root.setPadding(new Insets(8));
|
||||
|
||||
{
|
||||
StackPane left = new StackPane();
|
||||
left.setMaxSize(32, 32);
|
||||
left.setPrefSize(32, 32);
|
||||
left.getChildren().add(item.getIcon(24));
|
||||
this.left = new StackPane();
|
||||
left.setPadding(new Insets(0, 8, 0, 0));
|
||||
|
||||
Path path = item.getPath();
|
||||
if (path != null) {
|
||||
FXUtils.installSlowTooltip(left, path.toAbsolutePath().normalize().toString());
|
||||
}
|
||||
this.iconImageView = new ImageView();
|
||||
FXUtils.limitSize(iconImageView, 32, 32);
|
||||
|
||||
this.iconSVG = new SVGPath();
|
||||
iconSVG.getStyleClass().add("svg");
|
||||
iconSVG.setScaleX(32.0 / SVG.DEFAULT_SIZE);
|
||||
iconSVG.setScaleY(32.0 / SVG.DEFAULT_SIZE);
|
||||
|
||||
this.iconSVGWrapper = new StackPane(new Group(iconSVG));
|
||||
iconSVGWrapper.setAlignment(Pos.CENTER);
|
||||
FXUtils.setLimitWidth(iconSVGWrapper, 32);
|
||||
FXUtils.setLimitHeight(iconSVGWrapper, 32);
|
||||
|
||||
BorderPane.setAlignment(left, Pos.CENTER);
|
||||
root.setLeft(left);
|
||||
}
|
||||
|
||||
{
|
||||
TwoLineListItem center = new TwoLineListItem();
|
||||
center.setTitle(item.getName());
|
||||
center.setSubtitle(item.getDescription());
|
||||
|
||||
this.center = new TwoLineListItem();
|
||||
root.setCenter(center);
|
||||
}
|
||||
|
||||
if (!(item instanceof BackItem)) {
|
||||
HBox right = new HBox(8);
|
||||
{
|
||||
this.right = new HBox(8);
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
JFXButton btnReveal = new JFXButton();
|
||||
FXUtils.installFastTooltip(btnReveal, i18n("reveal.in_file_manager"));
|
||||
btnReveal.getStyleClass().add("toggle-icon4");
|
||||
btnReveal.setGraphic(SVG.FOLDER_OPEN.createIcon());
|
||||
btnReveal.setOnAction(event -> item.onReveal());
|
||||
btnReveal.setOnAction(event -> {
|
||||
Item item = getItem();
|
||||
if (item != null && !(item instanceof BackItem))
|
||||
item.onReveal();
|
||||
});
|
||||
|
||||
JFXButton btnDelete = new JFXButton();
|
||||
btnDelete.getStyleClass().add("toggle-icon4");
|
||||
btnDelete.setGraphic(SVG.DELETE_FOREVER.createIcon());
|
||||
btnDelete.setOnAction(event ->
|
||||
btnDelete.setOnAction(event -> {
|
||||
Item item = getItem();
|
||||
if (item != null && !(item instanceof BackItem)) {
|
||||
Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"),
|
||||
item::onDelete, null));
|
||||
item::onDelete, null);
|
||||
}
|
||||
});
|
||||
|
||||
right.getChildren().setAll(btnReveal, btnDelete);
|
||||
root.setRight(right);
|
||||
}
|
||||
|
||||
RipplerContainer container = new RipplerContainer(root);
|
||||
FXUtils.onClicked(container, item::onClick);
|
||||
this.getChildren().add(container);
|
||||
this.graphics = new RipplerContainer(root);
|
||||
FXUtils.onClicked(graphics, () -> {
|
||||
Item item = getItem();
|
||||
if (item != null)
|
||||
item.onClick();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(Item item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
iconImageView.setImage(null);
|
||||
|
||||
if (empty || item == null) {
|
||||
setGraphic(null);
|
||||
center.setTitle("");
|
||||
center.setSubtitle("");
|
||||
} else {
|
||||
if (item instanceof LitematicFileItem fileItem && fileItem.getImage() != null) {
|
||||
iconImageView.setImage(fileItem.getImage());
|
||||
left.getChildren().setAll(iconImageView);
|
||||
} else {
|
||||
iconSVG.setContent(item.getIcon().getPath());
|
||||
left.getChildren().setAll(iconSVGWrapper);
|
||||
}
|
||||
|
||||
center.setTitle(item.getName());
|
||||
center.setSubtitle(item.getDescription());
|
||||
|
||||
Path path = item.getPath();
|
||||
if (path != null) {
|
||||
tooltip.setText(FileUtils.getAbsolutePath(path));
|
||||
FXUtils.installSlowTooltip(left, tooltip);
|
||||
} else {
|
||||
tooltip.setText("");
|
||||
Tooltip.uninstall(left, tooltip);
|
||||
}
|
||||
|
||||
root.setRight(item instanceof BackItem ? null : right);
|
||||
|
||||
setGraphic(graphics);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,5 +674,10 @@ public final class SchematicsPage extends ListPageBase<SchematicsPage.Item> impl
|
||||
createToolbarButton2(i18n("schematics.create_directory"), SVG.CREATE_NEW_FOLDER, skinnable::onCreateDirectory)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ListCell<Item> createListCell(JFXListView<Item> listView) {
|
||||
return new Cell();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,7 @@ public class VersionIconDialog extends DialogPane {
|
||||
createIcon(VersionIconType.OPTIFINE),
|
||||
createIcon(VersionIconType.CRAFT_TABLE),
|
||||
createIcon(VersionIconType.FABRIC),
|
||||
createIcon(VersionIconType.LEGACY_FABRIC),
|
||||
createIcon(VersionIconType.FORGE),
|
||||
createIcon(VersionIconType.CLEANROOM),
|
||||
createIcon(VersionIconType.NEO_FORGE),
|
||||
|
||||
@@ -21,11 +21,8 @@ import com.jfoenix.controls.JFXPopup;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.EventPriority;
|
||||
@@ -305,20 +302,6 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
}
|
||||
}
|
||||
|
||||
public static Node wrap(Node node) {
|
||||
StackPane stackPane = new StackPane();
|
||||
stackPane.setAlignment(Pos.CENTER);
|
||||
FXUtils.setLimitWidth(stackPane, 30);
|
||||
FXUtils.setLimitHeight(stackPane, 20);
|
||||
stackPane.setPadding(new Insets(0, 0, 0, 0));
|
||||
stackPane.getChildren().setAll(node);
|
||||
return stackPane;
|
||||
}
|
||||
|
||||
public static Node wrap(SVG svg) {
|
||||
return wrap(svg.createIcon(20));
|
||||
}
|
||||
|
||||
public interface VersionLoadable {
|
||||
void loadVersion(Profile profile, String version);
|
||||
}
|
||||
|
||||
@@ -112,14 +112,12 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco
|
||||
|
||||
if (world.getGameVersion() != null && world.getGameVersion().isAtLeast("1.20", "23w14a")) {
|
||||
toolbar.addNavigationDrawerItem(i18n("version.launch"), SVG.ROCKET_LAUNCH, this::launch, advancedListItem -> advancedListItem.setDisable(isReadOnly()));
|
||||
toolbar.addNavigationDrawerItem(i18n("version.launch_script"), SVG.SCRIPT, this::generateLaunchScript, null);
|
||||
}
|
||||
|
||||
if (ChunkBaseApp.isSupported(world)) {
|
||||
PopupMenu chunkBasePopupMenu = new PopupMenu();
|
||||
JFXPopup chunkBasePopup = new JFXPopup(chunkBasePopupMenu);
|
||||
|
||||
|
||||
chunkBasePopupMenu.getContent().addAll(
|
||||
new IconedMenuItem(SVG.EXPLORE, i18n("world.chunkbase.seed_map"), () -> ChunkBaseApp.openSeedMap(world), chunkBasePopup),
|
||||
new IconedMenuItem(SVG.VISIBILITY, i18n("world.chunkbase.stronghold"), () -> ChunkBaseApp.openStrongholdFinder(world), chunkBasePopup),
|
||||
@@ -144,6 +142,14 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco
|
||||
PopupMenu managePopupMenu = new PopupMenu();
|
||||
JFXPopup managePopup = new JFXPopup(managePopupMenu);
|
||||
|
||||
if (world.getGameVersion() != null && world.getGameVersion().isAtLeast("1.20", "23w14a")) {
|
||||
managePopupMenu.getContent().addAll(
|
||||
new IconedMenuItem(SVG.ROCKET_LAUNCH, i18n("version.launch"), this::launch, managePopup),
|
||||
new IconedMenuItem(SVG.SCRIPT, i18n("version.launch_script"), this::generateLaunchScript, managePopup),
|
||||
new MenuSeparator()
|
||||
);
|
||||
}
|
||||
|
||||
managePopupMenu.getContent().addAll(
|
||||
new IconedMenuItem(SVG.OUTPUT, i18n("world.export"), () -> WorldManageUIUtils.export(world, sessionLockChannel), managePopup),
|
||||
new IconedMenuItem(SVG.DELETE, i18n("world.delete"), () -> WorldManageUIUtils.delete(world, () -> fireEvent(new PageCloseEvent()), sessionLockChannel), managePopup),
|
||||
|
||||
@@ -66,6 +66,8 @@ public abstract class RemoteImageLoader {
|
||||
cache.remove(uri);
|
||||
}
|
||||
|
||||
writableValue.setValue(getPlaceholder());
|
||||
|
||||
{
|
||||
List<WeakReference<WritableValue<Image>>> list = pendingRequests.get(uri);
|
||||
if (list != null) {
|
||||
|
||||
@@ -388,31 +388,33 @@
|
||||
-fx-cursor: hand;
|
||||
}
|
||||
|
||||
.installer-item {
|
||||
-fx-padding: 8px;
|
||||
.installer-item-wrapper {
|
||||
-fx-background-color: -monet-surface;
|
||||
-fx-background-radius: 4;
|
||||
-fx-pref-width: 180px;
|
||||
}
|
||||
|
||||
.installer-item:list-item {
|
||||
.installer-item-wrapper:card {
|
||||
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 10, 0.12, -1, 2);
|
||||
}
|
||||
|
||||
.installer-item-wrapper .installer-item:list-item {
|
||||
-fx-padding: 8px;
|
||||
-fx-border-color: -monet-outline-variant;
|
||||
-fx-border-width: 0 0 1 0;
|
||||
-fx-alignment: center-left;
|
||||
}
|
||||
|
||||
.installer-item:list-item > .installer-item-name {
|
||||
.installer-item-wrapper .installer-item:list-item > .installer-item-name {
|
||||
-fx-pref-width: 80px;
|
||||
}
|
||||
|
||||
.installer-item:list-item > .installer-item-status {
|
||||
.installer-item-wrapper .installer-item:list-item > .installer-item-status {
|
||||
-fx-max-width: infinity;
|
||||
}
|
||||
|
||||
.installer-item:card {
|
||||
-fx-background-color: -monet-surface;
|
||||
-fx-background-radius: 4;
|
||||
.installer-item-wrapper .installer-item:card {
|
||||
-fx-alignment: center;
|
||||
-fx-pref-width: 180px;
|
||||
|
||||
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 10, 0.12, -1, 2);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
@@ -719,7 +719,9 @@ install.installer.depend=Requires %s
|
||||
install.installer.do_not_install=Do not install
|
||||
install.installer.fabric=Fabric
|
||||
install.installer.fabric-api=Fabric API
|
||||
install.installer.fabric-api.warning=Warning: Fabric API is a mod and will be installed into the mod directory of the game instance. Please do not change the working directory of the game, or the Fabric API will not function. If you do want to change the directory, you should reinstall it.
|
||||
install.installer.legacyfabric=Legacy Fabric
|
||||
install.installer.legacyfabric-api=Legacy Fabric API
|
||||
install.installer.fabric-quilt-api.warning=%1$s is a mod and will be installed into the mod directory of the game instance. Please do not change the working directory of the game, or the %1$s will not function. If you do want to change the directory, you should reinstall it.
|
||||
install.installer.forge=Forge
|
||||
install.installer.neoforge=NeoForge
|
||||
install.installer.game=Minecraft
|
||||
@@ -1296,6 +1298,7 @@ settings.advanced.custom_commands.hint=The following environment variables are p
|
||||
\ · $INST_LITELOADER: set if LiteLoader is installed.\n\
|
||||
\ · $INST_OPTIFINE: set if OptiFine is installed.\n\
|
||||
\ · $INST_FABRIC: set if Fabric is installed.\n\
|
||||
\ · $INST_LEGACYFABRIC: set if Legacy Fabric is installed.\n\
|
||||
\ · $INST_QUILT: set if Quilt is installed.
|
||||
settings.advanced.dont_check_game_completeness=Do not check game integrity
|
||||
settings.advanced.dont_check_jvm_validity=Do not check JVM compatibility
|
||||
|
||||
@@ -696,7 +696,7 @@ install.installer.depend=يتطلب %s
|
||||
install.installer.do_not_install=عدم التثبيت
|
||||
install.installer.fabric=Fabric
|
||||
install.installer.fabric-api=Fabric API
|
||||
install.installer.fabric-api.warning=تحذير: Fabric API هو تعديل وسيتم تثبيته في دليل التعديلات لنسخة اللعبة. يرجى عدم تغيير دليل عمل اللعبة، أو لن يعمل Fabric API. إذا كنت تريد تغيير الدليل، فيجب عليك إعادة تثبيته.
|
||||
install.installer.fabric-quilt-api.warning=تحذير: %1$s هو تعديل وسيتم تثبيته في دليل التعديلات لنسخة اللعبة. يرجى عدم تغيير دليل عمل اللعبة، أو لن يعمل %1$s. إذا كنت تريد تغيير الدليل، فيجب عليك إعادة تثبيته.
|
||||
install.installer.forge=Forge
|
||||
install.installer.neoforge=NeoForge
|
||||
install.installer.game=Minecraft
|
||||
|
||||
@@ -701,7 +701,7 @@ install.installer.depend=Requiere %s
|
||||
install.installer.do_not_install=No instalar
|
||||
install.installer.fabric=Fabric
|
||||
install.installer.fabric-api=Fabric API
|
||||
install.installer.fabric-api.warning=Atención: Fabric API es un mod, y se instalará en el directorio de mods de la instancia del juego. Por favor, no cambies el directorio de trabajo del juego, o el Fabric API no funcionará. Si quieres cambiar esta configuración, debes reinstalarlo.
|
||||
install.installer.fabric-quilt-api.warning=%1$s es un mod, y se instalará en el directorio de mods de la instancia del juego. Por favor, no cambies el directorio de trabajo del juego, o el %1$s no funcionará. Si quieres cambiar esta configuración, debes reinstalarlo.
|
||||
install.installer.forge=Forge
|
||||
install.installer.neoforge=NeoForge
|
||||
install.installer.game=Minecraft
|
||||
|
||||
@@ -429,7 +429,7 @@ install.installer.choose=%s バージョンを選択してください
|
||||
install.installer.depend=%s に依存
|
||||
install.installer.fabric=Fabric
|
||||
install.installer.fabric-api=Fabric API
|
||||
install.installer.fabric-api.warning=警告:Fabric APIはmodであり、新しいゲームのmodディレクトリにインストールされます。新しいゲームの実行ディレクトリを変更しないでください。変更すると、FabricAPIのインストールが失われます。その設定を変更したい場合は、FabricAPIを再インストールする必要があります。
|
||||
install.installer.fabric-quilt-api.warning=%1$sはmodであり、新しいゲームのmodディレクトリにインストールされます。新しいゲームの実行ディレクトリを変更しないでください。変更すると、%1$sのインストールが失われます。その設定を変更したい場合は、%1$sを再インストールする必要があります。
|
||||
install.installer.forge=Forge
|
||||
install.installer.game=Minecraft
|
||||
install.installer.incompatible=%s と互換性がありません
|
||||
|
||||
@@ -498,7 +498,7 @@ install.installer.depend=請先裝 %s
|
||||
install.installer.do_not_install=不裝
|
||||
install.installer.fabric=緞
|
||||
install.installer.fabric-api=緞界
|
||||
install.installer.fabric-api.warning=誡:緞界者,改囊也,將措乎新戲之改囊案夾也。既裝而無改戲之「行徑」。將改,而須復置之。
|
||||
install.installer.fabric-quilt-api.warning=%1$s者,改囊也,將措乎新戲之改囊案夾也。既裝而無改戲之「行徑」。將改,而須復置之。
|
||||
install.installer.forge=鍛
|
||||
install.installer.neoforge=新鍛
|
||||
install.installer.game=礦藝
|
||||
|
||||
@@ -692,7 +692,7 @@ install.installer.depend=Требуется %s
|
||||
install.installer.do_not_install=Не устанавливать
|
||||
install.installer.fabric=Fabric
|
||||
install.installer.fabric-api=Fabric API
|
||||
install.installer.fabric-api.warning=Предупреждение: Fabric API является модом и будет установлен в папку mod в сборке игры. Пожалуйста, не меняйте рабочую папку сборки, иначе Fabric API не будет работать. Если вы все же хотите изменить папку, вам следует переустановить сборку.
|
||||
install.installer.fabric-quilt-api.warning=%1$s является модом и будет установлен в папку mod в сборке игры. Пожалуйста, не меняйте рабочую папку сборки, иначе %1$s не будет работать. Если вы все же хотите изменить папку, вам следует переустановить сборку.
|
||||
install.installer.forge=Forge
|
||||
install.installer.neoforge=NeoForge
|
||||
install.installer.game=Minecraft
|
||||
|
||||
@@ -640,7 +640,7 @@ install.installer.depend=Вимагає %s
|
||||
install.installer.do_not_install=Не встановлювати
|
||||
install.installer.fabric=Fabric
|
||||
install.installer.fabric-api=Fabric API
|
||||
install.installer.fabric-api.warning=Попередження: Fabric API - це мод, і він буде встановлений у каталог модів екземпляра гри. Не змінюйте робочий каталог гри, інакше Fabric API не працюватиме. Якщо ви все ж хочете змінити каталог, вам слід перевстановити його.
|
||||
install.installer.fabric-quilt-api.warning=%1$s - це мод, і він буде встановлений у каталог модів екземпляра гри. Не змінюйте робочий каталог гри, інакше %1$s не працюватиме. Якщо ви все ж хочете змінити каталог, вам слід перевстановити його.
|
||||
install.installer.forge=Forge
|
||||
install.installer.neoforge=NeoForge
|
||||
install.installer.game=Minecraft
|
||||
|
||||
@@ -518,7 +518,9 @@ install.installer.depend=需要先安裝 %s
|
||||
install.installer.do_not_install=不安裝
|
||||
install.installer.fabric=Fabric
|
||||
install.installer.fabric-api=Fabric API
|
||||
install.installer.fabric-api.warning=警告:Fabric API 是一個模組,將會被安裝到新遊戲的模組目錄。請你在安裝遊戲後不要修改目前遊戲的「執行路徑」設定。如果你在之後修改了相關設定,則需要重新安裝 Fabric API。
|
||||
install.installer.legacyfabric=Legacy Fabric
|
||||
install.installer.legacyfabric-api=Legacy Fabric API
|
||||
install.installer.fabric-quilt-api.warning=%1$s 是一個模組,將會被安裝到新遊戲的模組目錄。請你在安裝遊戲後不要修改目前遊戲的「執行路徑」設定。如果你在之後修改了相關設定,則需要重新安裝 %1$s。
|
||||
install.installer.forge=Forge
|
||||
install.installer.neoforge=NeoForge
|
||||
install.installer.game=Minecraft
|
||||
@@ -1080,6 +1082,7 @@ settings.advanced.custom_commands.hint=自訂指令被呼叫時將包含如下
|
||||
\ · $INST_LITELOADER: 若安裝了 LiteLoader,將會存在本環境變數;\n\
|
||||
\ · $INST_OPTIFINE: 若安裝了 OptiFine,將會存在本環境變數;\n\
|
||||
\ · $INST_FABRIC: 若安裝了 Fabric,將會存在本環境變數;\n\
|
||||
\ · $INST_LEGACYFABRIC: 若安裝了 Legacy Fabric,將會存在本環境變數;\n\
|
||||
\ · $INST_QUILT: 若安裝了 Quilt,將會存在本環境變數。
|
||||
settings.advanced.dont_check_game_completeness=不檢查遊戲完整性
|
||||
settings.advanced.dont_check_jvm_validity=不檢查 Java 虛擬機與遊戲的相容性
|
||||
|
||||
@@ -521,8 +521,10 @@ install.installer.cleanroom=Cleanroom
|
||||
install.installer.depend=需要先安装 %s
|
||||
install.installer.do_not_install=不安装
|
||||
install.installer.fabric=Fabric
|
||||
install.installer.legacyfabric=Legacy Fabric
|
||||
install.installer.legacyfabric-api=Legacy Fabric API
|
||||
install.installer.fabric-api=Fabric API
|
||||
install.installer.fabric-api.warning=警告:Fabric API 是一个模组,将会被安装到新游戏的模组文件夹。请你在安装游戏后不要修改当前游戏的“运行路径”设置。如果你在之后修改了相关设置,则需要重新安装 Fabric API。
|
||||
install.installer.fabric-quilt-api.warning=%1$s 是一个模组,将会被安装到新游戏的模组文件夹。请你在安装游戏后不要修改当前游戏的“版本隔离”设置。如果你在之后修改了相关设置,则需要重新安装 %1$s。
|
||||
install.installer.forge=Forge
|
||||
install.installer.neoforge=NeoForge
|
||||
install.installer.game=Minecraft
|
||||
@@ -1084,6 +1086,7 @@ settings.advanced.custom_commands.hint=自定义命令被调用时将包含如
|
||||
\ · $INST_LITELOADER: 若安装了 LiteLoader,将会存在本环境变量;\n\
|
||||
\ · $INST_OPTIFINE: 若安装了 OptiFine,将会存在本环境变量;\n\
|
||||
\ · $INST_FABRIC: 若安装了 Fabric,将会存在本环境变量;\n\
|
||||
\ · $INST_LEGACYFABRIC: 若安装了 Legacy Fabric,将会存在本环境变量;\n\
|
||||
\ · $INST_QUILT: 若安装了 Quilt,将会存在本环境变量。
|
||||
settings.advanced.dont_check_game_completeness=不检查游戏完整性
|
||||
settings.advanced.dont_check_jvm_validity=不检查 Java 虚拟机与游戏的兼容性
|
||||
|
||||
@@ -102,7 +102,19 @@ public final class Main {
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
private static void checkDirectoryPath() {
|
||||
String currentDir = System.getProperty("user.dir", "");
|
||||
if (currentDir.contains("!")) {
|
||||
SwingUtils.initLookAndFeel();
|
||||
System.err.println("The current working path contains an exclamation mark: " + currentDir);
|
||||
// No Chinese translation because both Swing and JavaFX cannot render Chinese character properly when exclamation mark exists in the path.
|
||||
SwingUtils.showErrorDialog("Exclamation mark(!) is not allowed in the path where HMCL is in.\n" + "The path is " + currentDir);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Throwable {
|
||||
checkDirectoryPath();
|
||||
if (getJavaFeatureVersion(System.getProperty("java.version")) >= MINIMUM_JAVA_VERSION) {
|
||||
EntryPoint.main(args);
|
||||
} else {
|
||||
|
||||
@@ -22,6 +22,8 @@ import org.jackhuang.hmcl.download.fabric.FabricAPIVersionList;
|
||||
import org.jackhuang.hmcl.download.fabric.FabricVersionList;
|
||||
import org.jackhuang.hmcl.download.forge.ForgeBMCLVersionList;
|
||||
import org.jackhuang.hmcl.download.game.GameVersionList;
|
||||
import org.jackhuang.hmcl.download.legacyfabric.LegacyFabricAPIVersionList;
|
||||
import org.jackhuang.hmcl.download.legacyfabric.LegacyFabricVersionList;
|
||||
import org.jackhuang.hmcl.download.liteloader.LiteLoaderBMCLVersionList;
|
||||
import org.jackhuang.hmcl.download.neoforge.NeoForgeBMCLVersionList;
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineBMCLVersionList;
|
||||
@@ -47,6 +49,8 @@ public final class BMCLAPIDownloadProvider implements DownloadProvider {
|
||||
private final FabricAPIVersionList fabricApi;
|
||||
private final ForgeBMCLVersionList forge;
|
||||
private final CleanroomVersionList cleanroom;
|
||||
private final LegacyFabricVersionList legacyFabric;
|
||||
private final LegacyFabricAPIVersionList legacyFabricApi;
|
||||
private final NeoForgeBMCLVersionList neoforge;
|
||||
private final LiteLoaderBMCLVersionList liteLoader;
|
||||
private final OptiFineBMCLVersionList optifine;
|
||||
@@ -66,6 +70,9 @@ public final class BMCLAPIDownloadProvider implements DownloadProvider {
|
||||
this.optifine = new OptiFineBMCLVersionList(apiRoot);
|
||||
this.quilt = new QuiltVersionList(this);
|
||||
this.quiltApi = new QuiltAPIVersionList(this);
|
||||
this.legacyFabric = new LegacyFabricVersionList(this);
|
||||
this.legacyFabricApi = new LegacyFabricAPIVersionList(this);
|
||||
|
||||
this.replacement = Arrays.asList(
|
||||
pair("https://bmclapi2.bangbang93.com", apiRoot),
|
||||
pair("https://launchermeta.mojang.com", apiRoot),
|
||||
@@ -112,30 +119,21 @@ public final class BMCLAPIDownloadProvider implements DownloadProvider {
|
||||
|
||||
@Override
|
||||
public VersionList<?> getVersionListById(String id) {
|
||||
switch (id) {
|
||||
case "game":
|
||||
return game;
|
||||
case "fabric":
|
||||
return fabric;
|
||||
case "fabric-api":
|
||||
return fabricApi;
|
||||
case "forge":
|
||||
return forge;
|
||||
case "cleanroom":
|
||||
return cleanroom;
|
||||
case "neoforge":
|
||||
return neoforge;
|
||||
case "liteloader":
|
||||
return liteLoader;
|
||||
case "optifine":
|
||||
return optifine;
|
||||
case "quilt":
|
||||
return quilt;
|
||||
case "quilt-api":
|
||||
return quiltApi;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unrecognized version list id: " + id);
|
||||
}
|
||||
return switch (id) {
|
||||
case "game" -> game;
|
||||
case "fabric" -> fabric;
|
||||
case "fabric-api" -> fabricApi;
|
||||
case "forge" -> forge;
|
||||
case "cleanroom" -> cleanroom;
|
||||
case "neoforge" -> neoforge;
|
||||
case "liteloader" -> liteLoader;
|
||||
case "optifine" -> optifine;
|
||||
case "quilt" -> quilt;
|
||||
case "quilt-api" -> quiltApi;
|
||||
case "legacyfabric" -> legacyFabric;
|
||||
case "legacyfabric-api" -> legacyFabricApi;
|
||||
default -> throw new IllegalArgumentException("Unrecognized version list id: " + id);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -189,7 +189,35 @@ public final class LibraryAnalyzer implements Iterable<LibraryAnalyzer.LibraryMa
|
||||
|
||||
public enum LibraryType {
|
||||
MINECRAFT(true, "game", "^$", "^$", null),
|
||||
FABRIC(true, "fabric", "net\\.fabricmc", "fabric-loader", ModLoaderType.FABRIC),
|
||||
LEGACY_FABRIC(true, "legacyfabric", "net\\.fabricmc", "fabric-loader", ModLoaderType.LEGACY_FABRIC) {
|
||||
@Override
|
||||
protected boolean matchLibrary(Library library, List<Library> libraries) {
|
||||
if (!super.matchLibrary(library, libraries)) {
|
||||
return false;
|
||||
}
|
||||
for (Library l : libraries) {
|
||||
if ("net.legacyfabric".equals(l.getGroupId())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
},
|
||||
LEGACY_FABRIC_API(false, "legacyfabric-api", "net\\.legacyfabric", "legacyfabric-api", null),
|
||||
FABRIC(true, "fabric", "net\\.fabricmc", "fabric-loader", ModLoaderType.FABRIC) {
|
||||
@Override
|
||||
protected boolean matchLibrary(Library library, List<Library> libraries) {
|
||||
if (!super.matchLibrary(library, libraries)) {
|
||||
return false;
|
||||
}
|
||||
for (Library l : libraries) {
|
||||
if ("net.legacyfabric".equals(l.getGroupId())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
FABRIC_API(true, "fabric-api", "net\\.fabricmc", "fabric-api", null),
|
||||
FORGE(true, "forge", "net\\.minecraftforge", "(forge|fmlloader)", ModLoaderType.FORGE) {
|
||||
private final Pattern FORGE_VERSION_MATCHER = Pattern.compile("^([0-9.]+)-(?<forge>[0-9.]+)(-([0-9.]+))?$");
|
||||
@@ -278,6 +306,7 @@ public final class LibraryAnalyzer implements Iterable<LibraryAnalyzer.LibraryMa
|
||||
private final ModLoaderType modLoaderType;
|
||||
|
||||
private static final Map<String, LibraryType> PATCH_ID_MAP = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (LibraryType type : values()) {
|
||||
PATCH_ID_MAP.put(type.getPatchId(), type);
|
||||
|
||||
@@ -22,6 +22,8 @@ import org.jackhuang.hmcl.download.fabric.FabricAPIVersionList;
|
||||
import org.jackhuang.hmcl.download.fabric.FabricVersionList;
|
||||
import org.jackhuang.hmcl.download.forge.ForgeVersionList;
|
||||
import org.jackhuang.hmcl.download.game.GameVersionList;
|
||||
import org.jackhuang.hmcl.download.legacyfabric.LegacyFabricAPIVersionList;
|
||||
import org.jackhuang.hmcl.download.legacyfabric.LegacyFabricVersionList;
|
||||
import org.jackhuang.hmcl.download.liteloader.LiteLoaderVersionList;
|
||||
import org.jackhuang.hmcl.download.neoforge.NeoForgeOfficialVersionList;
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineBMCLVersionList;
|
||||
@@ -47,6 +49,8 @@ public class MojangDownloadProvider implements DownloadProvider {
|
||||
private final OptiFineBMCLVersionList optifine;
|
||||
private final QuiltVersionList quilt;
|
||||
private final QuiltAPIVersionList quiltApi;
|
||||
private final LegacyFabricVersionList legacyFabric;
|
||||
private final LegacyFabricAPIVersionList legacyFabricApi;
|
||||
|
||||
public MojangDownloadProvider() {
|
||||
// If there is no official download channel available, fallback to BMCLAPI.
|
||||
@@ -62,6 +66,8 @@ public class MojangDownloadProvider implements DownloadProvider {
|
||||
this.optifine = new OptiFineBMCLVersionList(apiRoot);
|
||||
this.quilt = new QuiltVersionList(this);
|
||||
this.quiltApi = new QuiltAPIVersionList(this);
|
||||
this.legacyFabric = new LegacyFabricVersionList(this);
|
||||
this.legacyFabricApi = new LegacyFabricAPIVersionList(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -76,30 +82,21 @@ public class MojangDownloadProvider implements DownloadProvider {
|
||||
|
||||
@Override
|
||||
public VersionList<?> getVersionListById(String id) {
|
||||
switch (id) {
|
||||
case "game":
|
||||
return game;
|
||||
case "fabric":
|
||||
return fabric;
|
||||
case "fabric-api":
|
||||
return fabricApi;
|
||||
case "forge":
|
||||
return forge;
|
||||
case "cleanroom":
|
||||
return cleanroom;
|
||||
case "neoforge":
|
||||
return neoforge;
|
||||
case "liteloader":
|
||||
return liteLoader;
|
||||
case "optifine":
|
||||
return optifine;
|
||||
case "quilt":
|
||||
return quilt;
|
||||
case "quilt-api":
|
||||
return quiltApi;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unrecognized version list id: " + id);
|
||||
}
|
||||
return switch (id) {
|
||||
case "game" -> game;
|
||||
case "fabric" -> fabric;
|
||||
case "fabric-api" -> fabricApi;
|
||||
case "forge" -> forge;
|
||||
case "cleanroom" -> cleanroom;
|
||||
case "neoforge" -> neoforge;
|
||||
case "liteloader" -> liteLoader;
|
||||
case "optifine" -> optifine;
|
||||
case "quilt" -> quilt;
|
||||
case "quilt-api" -> quiltApi;
|
||||
case "legacyfabric" -> legacyFabric;
|
||||
case "legacyfabric-api" -> legacyFabricApi;
|
||||
default -> throw new IllegalArgumentException("Unrecognized version list id: " + id);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+4
-1
@@ -92,7 +92,10 @@ public final class CleanroomInstallTask extends Task<Version> {
|
||||
|
||||
@Override
|
||||
public void postExecute() throws Exception {
|
||||
Files.deleteIfExists(installer);
|
||||
if (remote != null) {
|
||||
Files.deleteIfExists(installer);
|
||||
}
|
||||
|
||||
setResult(task.getResult());
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ public final class FabricAPIInstallTask extends Task<Version> {
|
||||
public void execute() throws IOException {
|
||||
dependencies.add(new FileDownloadTask(
|
||||
remote.getVersion().getFile().getUrl(),
|
||||
dependencyManager.getGameRepository().getRunDirectory(version.getId()).resolve("mods").resolve("fabric-api-" + remote.getVersion().getVersion() + ".jar"),
|
||||
dependencyManager.getGameRepository().getModsDirectory(version.getId()).resolve("fabric-api-" + remote.getVersion().getVersion() + ".jar"),
|
||||
remote.getVersion().getFile().getIntegrityCheck())
|
||||
);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.gson.JsonSerializable;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -127,6 +128,7 @@ public final class FabricInstallTask extends Task<Version> {
|
||||
return new Version(LibraryAnalyzer.LibraryType.FABRIC.getPatchId(), loaderVersion, Version.PRIORITY_LOADER, arguments, mainClass, libraries);
|
||||
}
|
||||
|
||||
@JsonSerializable
|
||||
public static class FabricInfo {
|
||||
private final LoaderInfo loader;
|
||||
private final IntermediaryInfo intermediary;
|
||||
@@ -151,6 +153,7 @@ public final class FabricInstallTask extends Task<Version> {
|
||||
}
|
||||
}
|
||||
|
||||
@JsonSerializable
|
||||
public static class LoaderInfo {
|
||||
private final String separator;
|
||||
private final int build;
|
||||
@@ -187,6 +190,7 @@ public final class FabricInstallTask extends Task<Version> {
|
||||
}
|
||||
}
|
||||
|
||||
@JsonSerializable
|
||||
public static class IntermediaryInfo {
|
||||
private final String maven;
|
||||
private final String version;
|
||||
|
||||
+4
-4
@@ -21,7 +21,7 @@ import org.jackhuang.hmcl.download.ArtifactMalformedException;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.game.DownloadInfo;
|
||||
import org.jackhuang.hmcl.game.GameJavaVersion;
|
||||
import org.jackhuang.hmcl.java.*;
|
||||
import org.jackhuang.hmcl.java.JavaInfo;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
@@ -59,11 +59,11 @@ public final class MojangJavaDownloadTask extends Task<MojangJavaDownloadTask.Re
|
||||
MojangJavaDownloads allDownloads = JsonUtils.fromNonNullJson(javaDownloadsJson, MojangJavaDownloads.class);
|
||||
|
||||
Map<String, List<MojangJavaDownloads.JavaDownload>> osDownloads = allDownloads.getDownloads().get(platform);
|
||||
if (osDownloads == null || !osDownloads.containsKey(javaVersion.getComponent()))
|
||||
if (osDownloads == null || !osDownloads.containsKey(javaVersion.component()))
|
||||
throw new UnsupportedPlatformException("Unsupported platform: " + platform);
|
||||
List<MojangJavaDownloads.JavaDownload> candidates = osDownloads.get(javaVersion.getComponent());
|
||||
List<MojangJavaDownloads.JavaDownload> candidates = osDownloads.get(javaVersion.component());
|
||||
for (MojangJavaDownloads.JavaDownload download : candidates) {
|
||||
if (JavaInfo.parseVersion(download.getVersion().getName()) >= javaVersion.getMajorVersion()) {
|
||||
if (JavaInfo.parseVersion(download.getVersion().getName()) >= javaVersion.majorVersion()) {
|
||||
this.download = download;
|
||||
return new GetTask(downloadProvider.injectURLWithCandidates(download.getManifest().getUrl()));
|
||||
}
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ public final class MojangJavaRemoteVersion implements JavaRemoteVersion {
|
||||
|
||||
@Override
|
||||
public int getJdkVersion() {
|
||||
return gameJavaVersion.getMajorVersion();
|
||||
return gameJavaVersion.majorVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2021 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.download.legacyfabric;
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public final class LegacyFabricAPIInstallTask extends Task<Version> {
|
||||
|
||||
private final DefaultDependencyManager dependencyManager;
|
||||
private final Version version;
|
||||
private final LegacyFabricAPIRemoteVersion remote;
|
||||
private final List<Task<?>> dependencies = new ArrayList<>(1);
|
||||
|
||||
public LegacyFabricAPIInstallTask(DefaultDependencyManager dependencyManager, Version version, LegacyFabricAPIRemoteVersion remoteVersion) {
|
||||
this.dependencyManager = dependencyManager;
|
||||
this.version = version;
|
||||
this.remote = remoteVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Task<?>> getDependencies() {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRelyingOnDependencies() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws IOException {
|
||||
dependencies.add(new FileDownloadTask(
|
||||
remote.getVersion().getFile().getUrl(),
|
||||
dependencyManager.getGameRepository().getModsDirectory(version.getId()).resolve("legacy-fabric-api-" + remote.getVersion().getVersion() + ".jar"),
|
||||
remote.getVersion().getFile().getIntegrityCheck())
|
||||
);
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2022 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.download.legacyfabric;
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
public class LegacyFabricAPIRemoteVersion extends RemoteVersion {
|
||||
private final String fullVersion;
|
||||
private final RemoteMod.Version version;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param gameVersion the Minecraft version that this remote version suits.
|
||||
* @param selfVersion the version string of the remote version.
|
||||
* @param urls the installer or universal jar original URL.
|
||||
*/
|
||||
LegacyFabricAPIRemoteVersion(String gameVersion, String selfVersion, String fullVersion, Instant datePublished, RemoteMod.Version version, List<String> urls) {
|
||||
super(LibraryAnalyzer.LibraryType.LEGACY_FABRIC_API.getPatchId(), gameVersion, selfVersion, datePublished, urls);
|
||||
|
||||
this.fullVersion = fullVersion;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFullVersion() {
|
||||
return fullVersion;
|
||||
}
|
||||
|
||||
public RemoteMod.Version getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<Version> getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) {
|
||||
return new LegacyFabricAPIInstallTask(dependencyManager, baseVersion, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(RemoteVersion o) {
|
||||
if (!(o instanceof LegacyFabricAPIRemoteVersion)) return 0;
|
||||
return -this.getReleaseDate().compareTo(o.getReleaseDate());
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2022 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.download.legacyfabric;
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.VersionList;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
public class LegacyFabricAPIVersionList extends VersionList<LegacyFabricAPIRemoteVersion> {
|
||||
|
||||
private final DownloadProvider downloadProvider;
|
||||
|
||||
public LegacyFabricAPIVersionList(DownloadProvider downloadProvider) {
|
||||
this.downloadProvider = downloadProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasType() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<?> refreshAsync() {
|
||||
return Task.runAsync(() -> {
|
||||
for (RemoteMod.Version modVersion : Lang.toIterable(ModrinthRemoteModRepository.MODS.getRemoteVersionsById("legacy-fabric-api"))) {
|
||||
for (String gameVersion : modVersion.getGameVersions()) {
|
||||
versions.put(gameVersion, new LegacyFabricAPIRemoteVersion(gameVersion, modVersion.getVersion(), modVersion.getName(), modVersion.getDatePublished(), modVersion,
|
||||
Collections.singletonList(modVersion.getFile().getUrl())));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2022 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.download.legacyfabric;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.download.UnsupportedInstallationException;
|
||||
import org.jackhuang.hmcl.download.fabric.FabricInstallTask;
|
||||
import org.jackhuang.hmcl.game.Arguments;
|
||||
import org.jackhuang.hmcl.game.Artifact;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static org.jackhuang.hmcl.download.UnsupportedInstallationException.FABRIC_NOT_COMPATIBLE_WITH_FORGE;
|
||||
|
||||
public final class LegacyFabricInstallTask extends Task<Version> {
|
||||
|
||||
private final DefaultDependencyManager dependencyManager;
|
||||
private final Version version;
|
||||
private final LegacyFabricRemoteVersion remote;
|
||||
private final GetTask launchMetaTask;
|
||||
private final List<Task<?>> dependencies = new ArrayList<>(1);
|
||||
|
||||
public LegacyFabricInstallTask(DefaultDependencyManager dependencyManager, Version version, LegacyFabricRemoteVersion remoteVersion) {
|
||||
this.dependencyManager = dependencyManager;
|
||||
this.version = version;
|
||||
this.remote = remoteVersion;
|
||||
|
||||
launchMetaTask = new GetTask(dependencyManager.getDownloadProvider().injectURLsWithCandidates(remoteVersion.getUrls()));
|
||||
launchMetaTask.setCacheRepository(dependencyManager.getCacheRepository());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doPreExecute() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preExecute() throws Exception {
|
||||
if (!Objects.equals("net.minecraft.client.main.Main", version.resolve(dependencyManager.getGameRepository()).getMainClass()))
|
||||
throw new UnsupportedInstallationException(FABRIC_NOT_COMPATIBLE_WITH_FORGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Task<?>> getDependents() {
|
||||
return Collections.singleton(launchMetaTask);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Task<?>> getDependencies() {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRelyingOnDependencies() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
setResult(getPatch(JsonUtils.GSON.fromJson(launchMetaTask.getResult(), FabricInstallTask.FabricInfo.class), remote.getGameVersion(), remote.getSelfVersion()));
|
||||
|
||||
dependencies.add(dependencyManager.checkLibraryCompletionAsync(getResult(), true));
|
||||
}
|
||||
|
||||
private Version getPatch(FabricInstallTask.FabricInfo legacyFabricInfo, String gameVersion, String loaderVersion) {
|
||||
JsonObject launcherMeta = legacyFabricInfo.getLauncherMeta();
|
||||
Arguments arguments = new Arguments();
|
||||
|
||||
String mainClass;
|
||||
if (!launcherMeta.get("mainClass").isJsonObject()) {
|
||||
mainClass = launcherMeta.get("mainClass").getAsString();
|
||||
} else {
|
||||
mainClass = launcherMeta.get("mainClass").getAsJsonObject().get("client").getAsString();
|
||||
}
|
||||
|
||||
if (launcherMeta.has("launchwrapper")) {
|
||||
String clientTweaker = launcherMeta.get("launchwrapper").getAsJsonObject().get("tweakers").getAsJsonObject().get("client").getAsJsonArray().get(0).getAsString();
|
||||
arguments = arguments.addGameArguments("--tweakClass", clientTweaker);
|
||||
}
|
||||
|
||||
JsonObject librariesObject = launcherMeta.getAsJsonObject("libraries");
|
||||
List<Library> libraries = new ArrayList<>();
|
||||
|
||||
// "common, server" is hard coded in fabric installer.
|
||||
// Don't know the purpose of ignoring client libraries.
|
||||
for (String side : new String[]{"common", "server"}) {
|
||||
for (JsonElement element : librariesObject.getAsJsonArray(side)) {
|
||||
libraries.add(JsonUtils.GSON.fromJson(element, Library.class));
|
||||
}
|
||||
}
|
||||
|
||||
// libraries.add(new Library(Artifact.fromDescriptor(legacyFabricInfo.hashed.maven), getMavenRepositoryByGroup(legacyFabricInfo.hashed.maven), null));
|
||||
libraries.add(new Library(Artifact.fromDescriptor(legacyFabricInfo.getIntermediary().getMaven()), getMavenRepositoryByGroup(legacyFabricInfo.getIntermediary().getMaven()), null));
|
||||
libraries.add(new Library(Artifact.fromDescriptor(legacyFabricInfo.getLoader().getMaven()), getMavenRepositoryByGroup(legacyFabricInfo.getLoader().getMaven()), null));
|
||||
|
||||
return new Version(LibraryAnalyzer.LibraryType.LEGACY_FABRIC.getPatchId(), loaderVersion, Version.PRIORITY_LOADER, arguments, mainClass, libraries);
|
||||
}
|
||||
|
||||
private static String getMavenRepositoryByGroup(String maven) {
|
||||
Artifact artifact = Artifact.fromDescriptor(maven);
|
||||
return switch (artifact.getGroup()) {
|
||||
case "net.fabricmc" -> "https://maven.fabricmc.net/";
|
||||
case "net.legacyfabric" -> "https://maven.legacyfabric.net/";
|
||||
default -> "https://maven.fabricmc.net/";
|
||||
};
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2022 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.download.legacyfabric;
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class LegacyFabricRemoteVersion extends RemoteVersion {
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param gameVersion the Minecraft version that this remote version suits.
|
||||
* @param selfVersion the version string of the remote version.
|
||||
* @param urls the installer or universal jar original URL.
|
||||
*/
|
||||
LegacyFabricRemoteVersion(String gameVersion, String selfVersion, List<String> urls) {
|
||||
super(LibraryAnalyzer.LibraryType.LEGACY_FABRIC.getPatchId(), gameVersion, selfVersion, null, urls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<Version> getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) {
|
||||
return new LegacyFabricInstallTask(dependencyManager, baseVersion, this);
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2022 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.download.legacyfabric;
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.VersionList;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;
|
||||
|
||||
public final class LegacyFabricVersionList extends VersionList<LegacyFabricRemoteVersion> {
|
||||
private final DownloadProvider downloadProvider;
|
||||
|
||||
public LegacyFabricVersionList(DownloadProvider downloadProvider) {
|
||||
this.downloadProvider = downloadProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasType() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<?> refreshAsync() {
|
||||
return Task.runAsync(() -> {
|
||||
List<String> gameVersions = getGameVersions(GAME_META_URL);
|
||||
List<String> loaderVersions = getGameVersions(LOADER_META_URL);
|
||||
|
||||
lock.writeLock().lock();
|
||||
|
||||
try {
|
||||
for (String gameVersion : gameVersions)
|
||||
for (String loaderVersion : loaderVersions)
|
||||
versions.put(gameVersion, new LegacyFabricRemoteVersion(gameVersion, loaderVersion,
|
||||
Collections.singletonList(getLaunchMetaUrl(gameVersion, loaderVersion))));
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static final String LOADER_META_URL = "https://meta.legacyfabric.net/v2/versions/loader";
|
||||
private static final String GAME_META_URL = "https://meta.legacyfabric.net/v2/versions/game";
|
||||
|
||||
private List<String> getGameVersions(String metaUrl) throws IOException {
|
||||
String json = NetworkUtils.doGet(downloadProvider.injectURLWithCandidates(metaUrl));
|
||||
return JsonUtils.GSON.fromJson(json, listTypeOf(GameVersion.class))
|
||||
.stream().map(GameVersion::getVersion).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static String getLaunchMetaUrl(String gameVersion, String loaderVersion) {
|
||||
return String.format("https://meta.legacyfabric.net/v2/versions/loader/%s/%s", gameVersion, loaderVersion);
|
||||
}
|
||||
|
||||
private static class GameVersion {
|
||||
private final String version;
|
||||
private final String maven;
|
||||
private final boolean stable;
|
||||
|
||||
public GameVersion() {
|
||||
this("", null, false);
|
||||
}
|
||||
|
||||
public GameVersion(String version, String maven, boolean stable) {
|
||||
this.version = version;
|
||||
this.maven = maven;
|
||||
this.stable = stable;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getMaven() {
|
||||
return maven;
|
||||
}
|
||||
|
||||
public boolean isStable() {
|
||||
return stable;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,7 @@ public final class QuiltAPIInstallTask extends Task<Version> {
|
||||
public void execute() throws IOException {
|
||||
dependencies.add(new FileDownloadTask(
|
||||
remote.getVersion().getFile().getUrl(),
|
||||
dependencyManager.getGameRepository().getRunDirectory(version.getId()).resolve("mods").resolve("quilt-api-" + remote.getVersion().getVersion() + ".jar"),
|
||||
dependencyManager.getGameRepository().getModsDirectory(version.getId()).resolve("quilt-api-" + remote.getVersion().getVersion() + ".jar"),
|
||||
remote.getVersion().getFile().getIntegrityCheck())
|
||||
);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.gson.JsonSerializable;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
|
||||
import java.util.*;
|
||||
@@ -135,6 +136,7 @@ public final class QuiltInstallTask extends Task<Version> {
|
||||
}
|
||||
}
|
||||
|
||||
@JsonSerializable
|
||||
public static class QuiltInfo {
|
||||
private final LoaderInfo loader;
|
||||
private final IntermediaryInfo hashed;
|
||||
@@ -165,6 +167,7 @@ public final class QuiltInstallTask extends Task<Version> {
|
||||
}
|
||||
}
|
||||
|
||||
@JsonSerializable
|
||||
public static class LoaderInfo {
|
||||
private final String separator;
|
||||
private final int build;
|
||||
@@ -201,6 +204,7 @@ public final class QuiltInstallTask extends Task<Version> {
|
||||
}
|
||||
}
|
||||
|
||||
@JsonSerializable
|
||||
public static class IntermediaryInfo {
|
||||
private final String maven;
|
||||
private final String version;
|
||||
|
||||
@@ -17,14 +17,18 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import org.jackhuang.hmcl.util.gson.JsonSerializable;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.Platform;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public final class GameJavaVersion {
|
||||
@JsonSerializable
|
||||
public record GameJavaVersion(String component, int majorVersion) {
|
||||
public static final GameJavaVersion JAVA_25 = new GameJavaVersion("java-runtime-epsilon", 25);
|
||||
public static final GameJavaVersion JAVA_21 = new GameJavaVersion("java-runtime-delta", 21);
|
||||
public static final GameJavaVersion JAVA_17 = new GameJavaVersion("java-runtime-beta", 17);
|
||||
@@ -86,36 +90,13 @@ public final class GameJavaVersion {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private final String component;
|
||||
private final int majorVersion;
|
||||
|
||||
public GameJavaVersion() {
|
||||
this("", 0);
|
||||
}
|
||||
|
||||
public GameJavaVersion(String component, int majorVersion) {
|
||||
this.component = component;
|
||||
this.majorVersion = majorVersion;
|
||||
}
|
||||
|
||||
public String getComponent() {
|
||||
return component;
|
||||
}
|
||||
|
||||
public int getMajorVersion() {
|
||||
return majorVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getMajorVersion();
|
||||
return majorVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof GameJavaVersion)) return false;
|
||||
GameJavaVersion that = (GameJavaVersion) o;
|
||||
return majorVersion == that.majorVersion;
|
||||
return this == o || o instanceof GameJavaVersion that && this.majorVersion == that.majorVersion;
|
||||
}
|
||||
}
|
||||
@@ -18,9 +18,9 @@
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
@@ -43,7 +43,7 @@ public enum JavaVersionConstraint {
|
||||
@Override
|
||||
public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaRuntime java) {
|
||||
GameJavaVersion minimumJavaVersion = GameJavaVersion.getMinimumJavaVersion(gameVersionNumber);
|
||||
return minimumJavaVersion == null || java.getParsedVersion() >= minimumJavaVersion.getMajorVersion();
|
||||
return minimumJavaVersion == null || java.getParsedVersion() >= minimumJavaVersion.majorVersion();
|
||||
}
|
||||
},
|
||||
// Minecraft with suggested java version recorded in game json is restrictedly constrained.
|
||||
@@ -59,10 +59,10 @@ public enum JavaVersionConstraint {
|
||||
@Override
|
||||
public VersionRange<VersionNumber> getJavaVersionRange(Version version) {
|
||||
String javaVersion;
|
||||
if (Objects.requireNonNull(version.getJavaVersion()).getMajorVersion() >= 9) {
|
||||
javaVersion = "" + version.getJavaVersion().getMajorVersion();
|
||||
if (Objects.requireNonNull(version.getJavaVersion()).majorVersion() >= 9) {
|
||||
javaVersion = "" + version.getJavaVersion().majorVersion();
|
||||
} else {
|
||||
javaVersion = "1." + version.getJavaVersion().getMajorVersion();
|
||||
javaVersion = "1." + version.getJavaVersion().majorVersion();
|
||||
}
|
||||
return VersionNumber.atLeast(javaVersion);
|
||||
}
|
||||
@@ -247,9 +247,9 @@ public enum JavaVersionConstraint {
|
||||
return true;
|
||||
}
|
||||
|
||||
String versionNumber = gameJavaVersion.getMajorVersion() >= 9
|
||||
? String.valueOf(gameJavaVersion.getMajorVersion())
|
||||
: "1." + gameJavaVersion.getMajorVersion();
|
||||
String versionNumber = gameJavaVersion.majorVersion() >= 9
|
||||
? String.valueOf(gameJavaVersion.majorVersion())
|
||||
: "1." + gameJavaVersion.majorVersion();
|
||||
|
||||
VersionRange<VersionNumber> range = getJavaVersionRange(version);
|
||||
VersionNumber maximum = range.getMaximum();
|
||||
|
||||
@@ -21,7 +21,6 @@ import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.net.Proxy;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
@@ -49,11 +48,7 @@ public class LaunchOptions implements Serializable {
|
||||
private boolean fullscreen;
|
||||
private QuickPlayOption quickPlayOption;
|
||||
private String wrapper;
|
||||
private Proxy.Type proxyType;
|
||||
private String proxyHost;
|
||||
private int proxyPort;
|
||||
private String proxyUser;
|
||||
private String proxyPass;
|
||||
private ProxyOption proxyOption;
|
||||
private boolean noGeneratedJVMArgs;
|
||||
private boolean noGeneratedOptimizingJVMArgs;
|
||||
private String preLaunchCommand;
|
||||
@@ -195,30 +190,8 @@ public class LaunchOptions implements Serializable {
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
public Proxy.Type getProxyType() {
|
||||
return proxyType;
|
||||
}
|
||||
|
||||
public String getProxyHost() {
|
||||
return proxyHost;
|
||||
}
|
||||
|
||||
public int getProxyPort() {
|
||||
return proxyPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* The user name of the proxy, optional.
|
||||
*/
|
||||
public String getProxyUser() {
|
||||
return proxyUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* The password of the proxy, optional
|
||||
*/
|
||||
public String getProxyPass() {
|
||||
return proxyPass;
|
||||
public ProxyOption getProxyOption() {
|
||||
return proxyOption;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -422,28 +395,8 @@ public class LaunchOptions implements Serializable {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setProxyType(Proxy.Type proxyType) {
|
||||
options.proxyType = proxyType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setProxyHost(String proxyHost) {
|
||||
options.proxyHost = proxyHost;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setProxyPort(int proxyPort) {
|
||||
options.proxyPort = proxyPort;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setProxyUser(String proxyUser) {
|
||||
options.proxyUser = proxyUser;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setProxyPass(String proxyPass) {
|
||||
options.proxyPass = proxyPass;
|
||||
public Builder setProxyOption(ProxyOption proxyOption) {
|
||||
options.proxyOption = proxyOption;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2026 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.game;
|
||||
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/// @author Glavo
|
||||
public sealed interface ProxyOption {
|
||||
final class Direct implements ProxyOption {
|
||||
public static final Direct INSTANCE = new Direct();
|
||||
|
||||
private Direct() {
|
||||
}
|
||||
}
|
||||
|
||||
final class Default implements ProxyOption {
|
||||
public static final Default INSTANCE = new Default();
|
||||
|
||||
private Default() {
|
||||
}
|
||||
}
|
||||
|
||||
record Http(@NotNull String host, int port, @Nullable String username,
|
||||
@Nullable String password) implements ProxyOption {
|
||||
public Http {
|
||||
if (StringUtils.isBlank(host)) {
|
||||
throw new IllegalArgumentException("Host cannot be blank");
|
||||
}
|
||||
if (port < 0 || port > 0xFFFF) {
|
||||
throw new IllegalArgumentException("Illegal port: " + port);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
record Socks(@NotNull String host, int port, @Nullable String username,
|
||||
@Nullable String password) implements ProxyOption {
|
||||
public Socks {
|
||||
if (StringUtils.isBlank(host)) {
|
||||
throw new IllegalArgumentException("Host cannot be blank");
|
||||
}
|
||||
if (port < 0 || port > 0xFFFF) {
|
||||
throw new IllegalArgumentException("Illegal port: " + port);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,6 @@ import org.jackhuang.hmcl.util.platform.*;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.Proxy;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
@@ -73,14 +72,14 @@ public class DefaultLauncher extends Launcher {
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
// res.add("cmd", "/C", "start", "unused title", "/B", "/high");
|
||||
} else if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() || OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {
|
||||
res.add("nice", "-n", "-5");
|
||||
res.addAll("nice", "-n", "-5");
|
||||
}
|
||||
break;
|
||||
case ABOVE_NORMAL:
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
// res.add("cmd", "/C", "start", "unused title", "/B", "/abovenormal");
|
||||
} else if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() || OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {
|
||||
res.add("nice", "-n", "-1");
|
||||
res.addAll("nice", "-n", "-1");
|
||||
}
|
||||
break;
|
||||
case NORMAL:
|
||||
@@ -90,14 +89,14 @@ public class DefaultLauncher extends Launcher {
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
// res.add("cmd", "/C", "start", "unused title", "/B", "/belownormal");
|
||||
} else if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() || OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {
|
||||
res.add("nice", "-n", "1");
|
||||
res.addAll("nice", "-n", "1");
|
||||
}
|
||||
break;
|
||||
case LOW:
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
// res.add("cmd", "/C", "start", "unused title", "/B", "/low");
|
||||
} else if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() || OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {
|
||||
res.add("nice", "-n", "5");
|
||||
res.addAll("nice", "-n", "5");
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -170,22 +169,36 @@ public class DefaultLauncher extends Launcher {
|
||||
if (OperatingSystem.CURRENT_OS != OperatingSystem.WINDOWS)
|
||||
res.addDefault("-Duser.home=", options.getGameDir().toAbsolutePath().getParent().toString());
|
||||
|
||||
Proxy.Type proxyType = options.getProxyType();
|
||||
if (proxyType == null) {
|
||||
res.addDefault("-Djava.net.useSystemProxies", "true");
|
||||
} else {
|
||||
String proxyHost = options.getProxyHost();
|
||||
int proxyPort = options.getProxyPort();
|
||||
boolean addProxyOptions = res.noneMatch(arg ->
|
||||
arg.startsWith("-Djava.net.useSystemProxies=")
|
||||
|| arg.startsWith("-Dhttp.proxy")
|
||||
|| arg.startsWith("-Dhttps.proxy")
|
||||
|| arg.startsWith("-DsocksProxy")
|
||||
|| arg.startsWith("-Djava.net.socks.")
|
||||
);
|
||||
|
||||
if (StringUtils.isNotBlank(proxyHost) && proxyPort >= 0 && proxyPort <= 0xFFFF) {
|
||||
if (proxyType == Proxy.Type.HTTP) {
|
||||
res.addDefault("-Dhttp.proxyHost=", proxyHost);
|
||||
res.addDefault("-Dhttp.proxyPort=", String.valueOf(proxyPort));
|
||||
res.addDefault("-Dhttps.proxyHost=", proxyHost);
|
||||
res.addDefault("-Dhttps.proxyPort=", String.valueOf(proxyPort));
|
||||
} else if (proxyType == Proxy.Type.SOCKS) {
|
||||
res.addDefault("-DsocksProxyHost=", proxyHost);
|
||||
res.addDefault("-DsocksProxyPort=", String.valueOf(proxyPort));
|
||||
if (addProxyOptions) {
|
||||
if (options.getProxyOption() == null || options.getProxyOption() == ProxyOption.Default.INSTANCE) {
|
||||
res.add("-Djava.net.useSystemProxies=true");
|
||||
} else if (options.getProxyOption() instanceof ProxyOption.Http httpProxy) {
|
||||
res.add("-Dhttp.proxyHost=" + httpProxy.host());
|
||||
res.add("-Dhttp.proxyPort=" + httpProxy.port());
|
||||
res.add("-Dhttps.proxyHost=" + httpProxy.host());
|
||||
res.add("-Dhttps.proxyPort=" + httpProxy.port());
|
||||
|
||||
if (StringUtils.isNotBlank(httpProxy.username())) {
|
||||
res.add("-Dhttp.proxyUser=" + httpProxy.username());
|
||||
res.add("-Dhttp.proxyPassword=" + Objects.requireNonNullElse(httpProxy.password(), ""));
|
||||
res.add("-Dhttps.proxyUser=" + httpProxy.username());
|
||||
res.add("-Dhttps.proxyPassword=" + Objects.requireNonNullElse(httpProxy.password(), ""));
|
||||
}
|
||||
} else if (options.getProxyOption() instanceof ProxyOption.Socks socksProxy) {
|
||||
res.add("-DsocksProxyHost=" + socksProxy.host());
|
||||
res.add("-DsocksProxyPort=" + socksProxy.port());
|
||||
|
||||
if (StringUtils.isNotBlank(socksProxy.username())) {
|
||||
res.add("-Djava.net.socks.username=" + socksProxy.username());
|
||||
res.add("-Djava.net.socks.password=" + Objects.requireNonNullElse(socksProxy.password(), ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -230,7 +243,7 @@ public class DefaultLauncher extends Launcher {
|
||||
}
|
||||
}
|
||||
|
||||
if (is64bit && javaVersion == 25) {
|
||||
if (is64bit && (javaVersion >= 25 && javaVersion <= 26)) {
|
||||
res.addUnstableDefault("UseCompactObjectHeaders", true);
|
||||
}
|
||||
|
||||
@@ -334,21 +347,17 @@ public class DefaultLauncher extends Launcher {
|
||||
if (options.isFullscreen())
|
||||
res.add("--fullscreen");
|
||||
|
||||
if (options.getProxyType() == Proxy.Type.SOCKS) {
|
||||
String proxyHost = options.getProxyHost();
|
||||
int proxyPort = options.getProxyPort();
|
||||
|
||||
if (StringUtils.isNotBlank(proxyHost) && proxyPort >= 0 && proxyPort <= 0xFFFF) {
|
||||
res.add("--proxyHost");
|
||||
res.add(proxyHost);
|
||||
res.add("--proxyPort");
|
||||
res.add(String.valueOf(proxyPort));
|
||||
if (StringUtils.isNotBlank(options.getProxyUser()) && StringUtils.isNotBlank(options.getProxyPass())) {
|
||||
res.add("--proxyUser");
|
||||
res.add(options.getProxyUser());
|
||||
res.add("--proxyPass");
|
||||
res.add(options.getProxyPass());
|
||||
}
|
||||
// https://github.com/HMCL-dev/HMCL/issues/774
|
||||
if (options.getProxyOption() instanceof ProxyOption.Socks socksProxy) {
|
||||
res.add("--proxyHost");
|
||||
res.add(socksProxy.host());
|
||||
res.add("--proxyPort");
|
||||
res.add(String.valueOf(socksProxy.port()));
|
||||
if (StringUtils.isNotBlank(socksProxy.username())) {
|
||||
res.add("--proxyUser");
|
||||
res.add(socksProxy.username());
|
||||
res.add("--proxyPass");
|
||||
res.add(Objects.requireNonNullElse(socksProxy.password(), ""));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -600,6 +609,9 @@ public class DefaultLauncher extends Launcher {
|
||||
if (analyzer.has(LibraryAnalyzer.LibraryType.QUILT)) {
|
||||
env.put("INST_QUILT", "1");
|
||||
}
|
||||
if (analyzer.has(LibraryAnalyzer.LibraryType.LEGACY_FABRIC)) {
|
||||
env.put("INST_LEGACYFABRIC", "1");
|
||||
}
|
||||
|
||||
env.putAll(options.getEnvironmentVariables());
|
||||
|
||||
@@ -725,7 +737,7 @@ public class DefaultLauncher extends Launcher {
|
||||
writer.newLine();
|
||||
}
|
||||
writer.newLine();
|
||||
writer.write(new CommandBuilder().add("cd", "/D", FileUtils.getAbsolutePath(repository.getRunDirectory(version.getId()))).toString());
|
||||
writer.write(new CommandBuilder().addAll("cd", "/D", FileUtils.getAbsolutePath(repository.getRunDirectory(version.getId()))).toString());
|
||||
} else {
|
||||
writer.write("#!/usr/bin/env bash");
|
||||
writer.newLine();
|
||||
@@ -734,10 +746,10 @@ public class DefaultLauncher extends Launcher {
|
||||
writer.newLine();
|
||||
}
|
||||
if (commandLine.tempNativeFolder != null) {
|
||||
writer.write(new CommandBuilder().add("ln", "-s", FileUtils.getAbsolutePath(nativeFolder), commandLine.tempNativeFolder.toString()).toString());
|
||||
writer.write(new CommandBuilder().addAll("ln", "-s", FileUtils.getAbsolutePath(nativeFolder), commandLine.tempNativeFolder.toString()).toString());
|
||||
writer.newLine();
|
||||
}
|
||||
writer.write(new CommandBuilder().add("cd", FileUtils.getAbsolutePath(repository.getRunDirectory(version.getId()))).toString());
|
||||
writer.write(new CommandBuilder().addAll("cd", FileUtils.getAbsolutePath(repository.getRunDirectory(version.getId()))).toString());
|
||||
}
|
||||
writer.newLine();
|
||||
if (StringUtils.isNotBlank(options.getPreLaunchCommand())) {
|
||||
@@ -757,7 +769,7 @@ public class DefaultLauncher extends Launcher {
|
||||
writer.newLine();
|
||||
}
|
||||
if (commandLine.tempNativeFolder != null) {
|
||||
writer.write(new CommandBuilder().add("rm", commandLine.tempNativeFolder.toString()).toString());
|
||||
writer.write(new CommandBuilder().addAll("rm", commandLine.tempNativeFolder.toString()).toString());
|
||||
writer.newLine();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,11 @@ import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
@@ -182,9 +185,9 @@ public final class LocalModFile implements Comparable<LocalModFile> {
|
||||
.filter(version -> version.getLoaders().contains(getModLoaderType()))
|
||||
.filter(version -> version.getDatePublished().compareTo(currentVersion.get().getDatePublished()) > 0)
|
||||
.sorted(Comparator.comparing(RemoteMod.Version::getDatePublished).reversed())
|
||||
.collect(Collectors.toList());
|
||||
.toList();
|
||||
if (remoteVersions.isEmpty()) return null;
|
||||
return new ModUpdate(this, currentVersion.get(), remoteVersions);
|
||||
return new ModUpdate(this, currentVersion.get(), remoteVersions.get(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -205,12 +208,12 @@ public final class LocalModFile implements Comparable<LocalModFile> {
|
||||
public static class ModUpdate {
|
||||
private final LocalModFile localModFile;
|
||||
private final RemoteMod.Version currentVersion;
|
||||
private final List<RemoteMod.Version> candidates;
|
||||
private final RemoteMod.Version candidate;
|
||||
|
||||
public ModUpdate(LocalModFile localModFile, RemoteMod.Version currentVersion, List<RemoteMod.Version> candidates) {
|
||||
public ModUpdate(LocalModFile localModFile, RemoteMod.Version currentVersion, RemoteMod.Version candidate) {
|
||||
this.localModFile = localModFile;
|
||||
this.currentVersion = currentVersion;
|
||||
this.candidates = candidates;
|
||||
this.candidate = candidate;
|
||||
}
|
||||
|
||||
public LocalModFile getLocalMod() {
|
||||
@@ -221,8 +224,8 @@ public final class LocalModFile implements Comparable<LocalModFile> {
|
||||
return currentVersion;
|
||||
}
|
||||
|
||||
public List<RemoteMod.Version> getCandidates() {
|
||||
return candidates;
|
||||
public RemoteMod.Version getCandidate() {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,5 +25,6 @@ public enum ModLoaderType {
|
||||
FABRIC,
|
||||
QUILT,
|
||||
LITE_LOADER,
|
||||
PACK;
|
||||
LEGACY_FABRIC,
|
||||
PACK
|
||||
}
|
||||
@@ -26,7 +26,6 @@ import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.tree.ZipFileTree;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -177,11 +176,13 @@ public final class ModManager {
|
||||
|
||||
analyzer = LibraryAnalyzer.analyze(getRepository().getResolvedPreservingPatchesVersion(id), null);
|
||||
|
||||
boolean supportSubfolders = analyzer.has(LibraryAnalyzer.LibraryType.FORGE)
|
||||
|| analyzer.has(LibraryAnalyzer.LibraryType.QUILT);
|
||||
|
||||
if (Files.isDirectory(getModsDirectory())) {
|
||||
try (DirectoryStream<Path> modsDirectoryStream = Files.newDirectoryStream(getModsDirectory())) {
|
||||
for (Path subitem : modsDirectoryStream) {
|
||||
if (Files.isDirectory(subitem) && VersionNumber.isIntVersionNumber(FileUtils.getName(subitem))) {
|
||||
// If the folder name is game version, forge will search mod in this subdirectory
|
||||
if (supportSubfolders && Files.isDirectory(subitem)) {
|
||||
try (DirectoryStream<Path> subitemDirectoryStream = Files.newDirectoryStream(subitem)) {
|
||||
for (Path subsubitem : subitemDirectoryStream) {
|
||||
addModInfo(subsubitem);
|
||||
|
||||
@@ -109,6 +109,8 @@ public class McbbsModpackExportTask extends Task<Void> {
|
||||
addons.add(new McbbsModpackManifest.Addon(FABRIC.getPatchId(), fabricVersion)));
|
||||
analyzer.getVersion(QUILT).ifPresent(quiltVersion ->
|
||||
addons.add(new McbbsModpackManifest.Addon(QUILT.getPatchId(), quiltVersion)));
|
||||
analyzer.getVersion(LEGACY_FABRIC).ifPresent(legacyfabricVersion ->
|
||||
addons.add(new McbbsModpackManifest.Addon(LEGACY_FABRIC.getPatchId(), legacyfabricVersion)));
|
||||
|
||||
List<Library> libraries = new ArrayList<>();
|
||||
// TODO libraries
|
||||
|
||||
+1
-1
@@ -164,7 +164,7 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
SEMAPHORE.acquireUninterruptibly();
|
||||
try {
|
||||
id = StringUtils.removePrefix(id, "local-");
|
||||
List<ProjectVersion> versions = HttpRequest.GET(PREFIX + "/v2/project/" + id + "/version")
|
||||
List<ProjectVersion> versions = HttpRequest.GET(PREFIX + "/v2/project/" + id + "/version?include_changelog=false")
|
||||
.getJson(listTypeOf(ProjectVersion.class));
|
||||
return versions.stream().map(ProjectVersion::toVersion).flatMap(Lang::toStream);
|
||||
} finally {
|
||||
|
||||
@@ -152,7 +152,7 @@ public final class Unzipper {
|
||||
}
|
||||
}
|
||||
|
||||
if (entryCount == 0 && !terminateIfSubDirectoryNotExists) {
|
||||
if (entryCount == 0 && !"/".equals(subDirectory) && !terminateIfSubDirectoryNotExists) {
|
||||
throw new NoSuchFileException("Subdirectory " + subDirectory + " does not exist in the zip file.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,13 +48,24 @@ public final class CommandBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parsing will ignore your manual escaping
|
||||
*
|
||||
* @param arg command
|
||||
* @return this
|
||||
*/
|
||||
public CommandBuilder add(String arg) {
|
||||
raw.add(new Item(arg, true));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parsing will ignore your manual escaping
|
||||
*
|
||||
* @param args commands
|
||||
* @return this
|
||||
*/
|
||||
public CommandBuilder add(String... args) {
|
||||
public CommandBuilder addAll(String... args) {
|
||||
for (String s : args)
|
||||
raw.add(new Item(s, true));
|
||||
return this;
|
||||
|
||||
@@ -795,7 +795,8 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
|
||||
} else if (version instanceof Release release) {
|
||||
currentRelease = release;
|
||||
|
||||
if (currentRelease.eaType == Release.ReleaseType.GA) {
|
||||
if (currentRelease.eaType == Release.ReleaseType.GA
|
||||
&& currentRelease.additional == Release.Additional.NONE) {
|
||||
defaultGameVersions.addFirst(currentRelease.value);
|
||||
}
|
||||
} else if (version instanceof Special special) {
|
||||
|
||||
@@ -15,4 +15,4 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
versionRoot=3.10
|
||||
versionRoot=3.11
|
||||
Reference in new issue
Block a user