Support fabric auto-installation

This commit is contained in:
huanghongxun
2019-08-16 12:35:56 +08:00
parent d53d56d93a
commit eb6baa8ad1
31 changed files with 484 additions and 176 deletions

View File

@@ -23,6 +23,7 @@ import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.LibraryAnalyzer;
import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.game.GameRepository;
import org.jackhuang.hmcl.ui.FXUtils;
@@ -33,6 +34,7 @@ import org.jackhuang.hmcl.util.Lang;
import java.util.Map;
import java.util.Optional;
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
class AdditionalInstallersPage extends StackPane implements WizardPage {
@@ -42,6 +44,8 @@ class AdditionalInstallersPage extends StackPane implements WizardPage {
@FXML
private VBox list;
@FXML
private JFXButton btnFabric;
@FXML
private JFXButton btnForge;
@FXML
private JFXButton btnLiteLoader;
@@ -52,6 +56,8 @@ class AdditionalInstallersPage extends StackPane implements WizardPage {
@FXML
private Label lblVersionName;
@FXML
private Label lblFabric;
@FXML
private Label lblForge;
@FXML
private Label lblLiteLoader;
@@ -69,26 +75,17 @@ class AdditionalInstallersPage extends StackPane implements WizardPage {
lblGameVersion.setText(provider.getGameVersion());
lblVersionName.setText(provider.getVersion().getId());
btnForge.setOnMouseClicked(e -> {
controller.getSettings().put(INSTALLER_TYPE, 0);
controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.forge")), provider.getGameVersion(), downloadProvider, "forge", () -> {
controller.onPrev(false);
}));
});
JFXButton[] buttons = new JFXButton[]{btnFabric, btnForge, btnLiteLoader, btnOptiFine};
String[] libraryIds = new String[]{"fabric", "forge", "liteloader", "optifine"};
btnLiteLoader.setOnMouseClicked(e -> {
controller.getSettings().put(INSTALLER_TYPE, 1);
controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.liteloader")), provider.getGameVersion(), downloadProvider, "liteloader", () -> {
controller.onPrev(false);
}));
});
btnOptiFine.setOnMouseClicked(e -> {
controller.getSettings().put(INSTALLER_TYPE, 2);
controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.optifine")), provider.getGameVersion(), downloadProvider, "optifine", () -> {
controller.onPrev(false);
}));
});
for (int i = 0; i < libraryIds.length; ++i) {
String libraryId = libraryIds[i];
buttons[i].setOnMouseClicked(e -> {
controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer." + libraryId)), provider.getGameVersion(), downloadProvider, libraryId, () -> {
controller.onPrev(false);
}));
});
}
btnInstall.setOnMouseClicked(e -> onInstall());
}
@@ -109,30 +106,29 @@ class AdditionalInstallersPage extends StackPane implements WizardPage {
@Override
public void onNavigate(Map<String, Object> settings) {
lblGameVersion.setText(i18n("install.new_game.current_game_version") + ": " + provider.getGameVersion());
btnForge.setDisable(provider.getForge() != null);
if (provider.getForge() != null || controller.getSettings().containsKey("forge"))
lblForge.setText(i18n("install.installer.version", i18n("install.installer.forge")) + ": " + Lang.nonNull(provider.getForge(), getVersion("forge")));
else
lblForge.setText(i18n("install.installer.not_installed", i18n("install.installer.forge")));
btnLiteLoader.setDisable(provider.getLiteLoader() != null);
if (provider.getLiteLoader() != null || controller.getSettings().containsKey("liteloader"))
lblLiteLoader.setText(i18n("install.installer.version", i18n("install.installer.liteloader")) + ": " + Lang.nonNull(provider.getLiteLoader(), getVersion("liteloader")));
else
lblLiteLoader.setText(i18n("install.installer.not_installed", i18n("install.installer.liteloader")));
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(provider.getVersion().resolve(provider.getProfile().getRepository()));
String fabric = analyzer.getVersion(FABRIC).orElse(null);
String forge = analyzer.getVersion(FORGE).orElse(null);
String liteLoader = analyzer.getVersion(LITELOADER).orElse(null);
String optiFine = analyzer.getVersion(OPTIFINE).orElse(null);
btnOptiFine.setDisable(provider.getOptiFine() != null);
if (provider.getOptiFine() != null || controller.getSettings().containsKey("optifine"))
lblOptiFine.setText(i18n("install.installer.version", i18n("install.installer.optifine")) + ": " + Lang.nonNull(provider.getOptiFine(), getVersion("optifine")));
else
lblOptiFine.setText(i18n("install.installer.not_installed", i18n("install.installer.optifine")));
JFXButton[] buttons = new JFXButton[]{btnFabric, btnForge, btnLiteLoader, btnOptiFine};
Label[] labels = new Label[]{lblFabric, lblForge, lblLiteLoader, lblOptiFine};
String[] libraryIds = new String[]{"fabric", "forge", "liteloader", "optifine"};
String[] versions = new String[]{fabric, forge, liteLoader, optiFine};
for (int i = 0; i < libraryIds.length; ++i) {
String libraryId = libraryIds[i];
buttons[i].setDisable(versions[i] != null);
if (versions[i] != null || controller.getSettings().containsKey(libraryId))
labels[i].setText(i18n("install.installer.version", i18n("install.installer." + libraryId)) + ": " + Lang.nonNull(versions[i], getVersion(libraryId)));
else
labels[i].setText(i18n("install.installer.not_installed", i18n("install.installer." + libraryId)));
}
}
@Override
public void cleanup(Map<String, Object> settings) {
settings.remove(INSTALLER_TYPE);
}
public static final String INSTALLER_TYPE = "INSTALLER_TYPE";
}

View File

@@ -19,7 +19,6 @@ package org.jackhuang.hmcl.ui.download;
import javafx.scene.Node;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.LibraryAnalyzer;
import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.download.VersionMismatchException;
import org.jackhuang.hmcl.download.game.LibraryDownloadException;
@@ -39,26 +38,17 @@ import org.jackhuang.hmcl.util.io.ResponseCodeException;
import java.net.SocketTimeoutException;
import java.util.Map;
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class InstallerWizardProvider implements WizardProvider {
private final Profile profile;
private final String gameVersion;
private final Version version;
private final String forge;
private final String liteLoader;
private final String optiFine;
public InstallerWizardProvider(Profile profile, String gameVersion, Version version) {
this.profile = profile;
this.gameVersion = gameVersion;
this.version = version;
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version.resolve(profile.getRepository()));
forge = analyzer.getVersion(FORGE).orElse(null);
liteLoader = analyzer.getVersion(LITELOADER).orElse(null);
optiFine = analyzer.getVersion(OPTIFINE).orElse(null);
}
public Profile getProfile() {
@@ -73,18 +63,6 @@ public final class InstallerWizardProvider implements WizardProvider {
return version;
}
public String getForge() {
return forge;
}
public String getLiteLoader() {
return liteLoader;
}
public String getOptiFine() {
return optiFine;
}
@Override
public void start(Map<String, Object> settings) {
}
@@ -96,14 +74,10 @@ public final class InstallerWizardProvider implements WizardProvider {
Task<Version> ret = Task.supplyAsync(() -> version);
if (settings.containsKey("forge"))
ret = ret.thenComposeAsync(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("forge")));
if (settings.containsKey("liteloader"))
ret = ret.thenComposeAsync(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("liteloader")));
if (settings.containsKey("optifine"))
ret = ret.thenComposeAsync(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("optifine")));
for (Object value : settings.values()) {
if (value instanceof RemoteVersion)
ret = ret.thenComposeAsync(profile.getDependency().installLibraryAsync((RemoteVersion) value));
}
return ret.thenComposeAsync(profile.getRepository().refreshVersionsAsync());
}

View File

@@ -42,6 +42,9 @@ public class InstallersPage extends StackPane implements WizardPage {
@FXML
private VBox list;
@FXML
private JFXButton btnFabric;
@FXML
private JFXButton btnForge;
@@ -54,6 +57,9 @@ public class InstallersPage extends StackPane implements WizardPage {
@FXML
private Label lblGameVersion;
@FXML
private Label lblFabric;
@FXML
private Label lblForge;
@@ -81,20 +87,14 @@ public class InstallersPage extends StackPane implements WizardPage {
txtName.textProperty().addListener(e -> btnInstall.setDisable(!txtName.validate()));
txtName.setText(gameVersion);
btnForge.setOnMouseClicked(e -> {
controller.getSettings().put(INSTALLER_TYPE, 0);
controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.forge")), gameVersion, downloadProvider, "forge", () -> controller.onPrev(false)));
});
JFXButton[] buttons = new JFXButton[]{btnFabric, btnForge, btnLiteLoader, btnOptiFine};
String[] libraryIds = new String[]{"fabric", "forge", "liteloader", "optifine"};
btnLiteLoader.setOnMouseClicked(e -> {
controller.getSettings().put(INSTALLER_TYPE, 1);
controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.liteloader")), gameVersion, downloadProvider, "liteloader", () -> controller.onPrev(false)));
});
btnOptiFine.setOnMouseClicked(e -> {
controller.getSettings().put(INSTALLER_TYPE, 2);
controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.optifine")), gameVersion, downloadProvider, "optifine", () -> controller.onPrev(false)));
});
for (int i = 0; i < libraryIds.length; ++i) {
String libraryId = libraryIds[i];
buttons[i].setOnMouseClicked(e ->
controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer." + libraryId)), gameVersion, downloadProvider, libraryId, () -> controller.onPrev(false))));
}
}
@Override
@@ -109,25 +109,21 @@ public class InstallersPage extends StackPane implements WizardPage {
@Override
public void onNavigate(Map<String, Object> settings) {
lblGameVersion.setText(i18n("install.new_game.current_game_version") + ": " + getVersion("game"));
if (controller.getSettings().containsKey("forge"))
lblForge.setText(i18n("install.installer.version", i18n("install.installer.forge")) + ": " + getVersion("forge"));
else
lblForge.setText(i18n("install.installer.not_installed", i18n("install.installer.forge")));
if (controller.getSettings().containsKey("liteloader"))
lblLiteLoader.setText(i18n("install.installer.version", i18n("install.installer.liteloader")) + ": " + getVersion("liteloader"));
else
lblLiteLoader.setText(i18n("install.installer.not_installed", i18n("install.installer.liteloader")));
Label[] labels = new Label[]{lblFabric, lblForge, lblLiteLoader, lblOptiFine};
String[] libraryIds = new String[]{"fabric", "forge", "liteloader", "optifine"};
if (controller.getSettings().containsKey("optifine"))
lblOptiFine.setText(i18n("install.installer.version", i18n("install.installer.optifine")) + ": " + getVersion("optifine"));
else
lblOptiFine.setText(i18n("install.installer.not_installed", i18n("install.installer.optifine")));
for (int i = 0; i < libraryIds.length; ++i) {
String libraryId = libraryIds[i];
if (controller.getSettings().containsKey(libraryId))
labels[i].setText(i18n("install.installer.version", i18n("install.installer." + libraryId)) + ": " + getVersion(libraryId));
else
labels[i].setText(i18n("install.installer.not_installed", i18n("install.installer." + libraryId)));
}
}
@Override
public void cleanup(Map<String, Object> settings) {
settings.remove(INSTALLER_TYPE);
}
@FXML
@@ -135,6 +131,4 @@ public class InstallersPage extends StackPane implements WizardPage {
controller.getSettings().put("name", txtName.getText());
controller.onFinish();
}
public static final String INSTALLER_TYPE = "INSTALLER_TYPE";
}

View File

@@ -50,14 +50,9 @@ public final class VanillaInstallWizardProvider implements WizardProvider {
builder.name(name);
builder.gameVersion(((RemoteVersion) settings.get("game")).getGameVersion());
if (settings.containsKey("forge"))
builder.version((RemoteVersion) settings.get("forge"));
if (settings.containsKey("liteloader"))
builder.version((RemoteVersion) settings.get("liteloader"));
if (settings.containsKey("optifine"))
builder.version((RemoteVersion) settings.get("optifine"));
for (Map.Entry<String, Object> entry : settings.entrySet())
if (!"game".equals(entry.getKey()) && entry.getValue() instanceof RemoteVersion)
builder.version((RemoteVersion) entry.getValue());
return builder.buildAsync().whenComplete(any -> profile.getRepository().refreshVersions())
.thenRunAsync(Schedulers.javafx(), () -> profile.setSelectedVersion(name));

View File

@@ -33,7 +33,6 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*;
import static org.jackhuang.hmcl.util.Lang.handleUncaught;
import static org.jackhuang.hmcl.util.Lang.threadPool;
import static org.jackhuang.hmcl.util.StringUtils.removePrefix;
@@ -58,10 +57,13 @@ public class GameItem extends Control {
CompletableFuture.supplyAsync(() -> GameVersion.minecraftVersion(profile.getRepository().getVersionJar(id)).orElse(i18n("message.unknown")), POOL_VERSION_RESOLVE)
.thenAcceptAsync(game -> {
StringBuilder libraries = new StringBuilder(game);
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(profile.getRepository().getVersion(id));
analyzer.getVersion(FORGE).ifPresent(library -> libraries.append(", ").append(i18n("install.installer.forge")).append(": ").append(modifyVersion(game, library.replaceAll("(?i)forge", ""))));
analyzer.getVersion(LITELOADER).ifPresent(library -> libraries.append(", ").append(i18n("install.installer.liteloader")).append(": ").append(modifyVersion(game, library.replaceAll("(?i)liteloader", ""))));
analyzer.getVersion(OPTIFINE).ifPresent(library -> libraries.append(", ").append(i18n("install.installer.optifine")).append(": ").append(modifyVersion(game, library.replaceAll("(?i)optifine", ""))));
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(profile.getRepository().getResolvedVersion(id));
for (LibraryAnalyzer.LibraryType type : LibraryAnalyzer.LibraryType.values())
analyzer.getVersion(type).ifPresent(library ->
libraries
.append(", ").append(i18n("install.installer." + type.name().toLowerCase()))
.append(": ").append(modifyVersion(game, library.replaceAll("(?i)" + type.name().toLowerCase(), ""))));
subtitle.set(libraries.toString());
}, Platform::runLater)
.exceptionally(handleUncaught);

View File

@@ -44,7 +44,6 @@ import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*;
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
@@ -85,19 +84,13 @@ public class InstallerListPage extends ListPageBase<InstallerItem> {
};
itemsProperty().clear();
analyzer.getVersion(FORGE).ifPresent(libraryVersion -> itemsProperty().add(
new InstallerItem("Forge", libraryVersion, () -> {
Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, "forge", libraryVersion));
}, removeAction.apply("forge"))));
analyzer.getVersion(LITELOADER).ifPresent(libraryVersion -> itemsProperty().add(
new InstallerItem("LiteLoader", libraryVersion, () -> {
Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, "liteloader", libraryVersion));
}, removeAction.apply("liteloader"))));
analyzer.getVersion(OPTIFINE).ifPresent(libraryVersion -> itemsProperty().add(
new InstallerItem("OptiFine", libraryVersion, () -> {
Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, "optifine", libraryVersion));
}, removeAction.apply("optifine"))));
analyzer.getVersion(FABRIC).ifPresent(libraryVersion -> itemsProperty().add(new InstallerItem("Fabric", libraryVersion, null, null)));
for (LibraryAnalyzer.LibraryType type : LibraryAnalyzer.LibraryType.values()) {
String libraryId = type.getPatchId();
analyzer.getVersion(type).ifPresent(libraryVersion -> itemsProperty().add(
new InstallerItem(i18n("install.installer." + libraryId), libraryVersion, () -> {
Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, libraryId, libraryVersion));
}, removeAction.apply(libraryId))));
}
}).start();
}

View File

@@ -15,6 +15,18 @@
</top>
<center>
<VBox fx:id="list" styleClass="jfx-list-view" maxHeight="150" maxWidth="300">
<JFXButton fx:id="btnFabric" prefWidth="${list.width}">
<graphic>
<BorderPane mouseTransparent="true">
<left>
<Label fx:id="lblFabric"/>
</left>
<right>
<fx:include source="/assets/svg/arrow-right.fxml"/>
</right>
</BorderPane>
</graphic>
</JFXButton>
<JFXButton fx:id="btnForge" prefWidth="${list.width}">
<graphic>
<BorderPane mouseTransparent="true">

View File

@@ -15,6 +15,18 @@
</top>
<center>
<VBox fx:id="list" styleClass="jfx-list-view" maxHeight="150" maxWidth="300">
<JFXButton fx:id="btnFabric" prefWidth="${list.width}">
<graphic>
<BorderPane mouseTransparent="true">
<left>
<Label fx:id="lblFabric"/>
</left>
<right>
<fx:include source="/assets/svg/arrow-right.fxml"/>
</right>
</BorderPane>
</graphic>
</JFXButton>
<JFXButton fx:id="btnForge" prefWidth="${list.width}">
<graphic>
<BorderPane mouseTransparent="true">

View File

@@ -135,6 +135,7 @@ install.failed.install_online=Unable to recognize what you provided installer fi
install.failed.optifine_conflict=OptiFine and Forge are both installed simultaneously on Minecraft 1.13
install.failed.version_mismatch=The library requires game version %s, but actual version is %s.
install.installer.choose=Choose a %s version
install.installer.fabric=Fabric
install.installer.forge=Forge
install.installer.game=Game
install.installer.install=Install %s

View File

@@ -135,6 +135,7 @@ install.failed.install_online=No se pudo reconocer el archivo instalador proveí
install.failed.optifine_conflict=OptiFine y Forge ambos están instalados simultáneamente en Minecraft 1.13
install.failed.version_mismatch=El library requiere versión del juego %s, pero versión actual es %s.
install.installer.choose=Escoja una versión de %s
install.installer.fabric=Fabric
install.installer.forge=Forge
install.installer.game=Juego
install.installer.install=Instalar %s

View File

@@ -131,9 +131,10 @@ install.failed.downloading=安装失败,部分文件未能完成下载
install.failed.downloading.detail=未能下载文件:%s
install.failed.downloading.timeout=下载超时:%s
install.failed.install_online=无法识别要安装的软件
install.failed.optifine_conflict=暂不支持 OptiFine 与 Forge 同时安装在 Minecraft 1.13
install.failed.optifine_conflict=暂不支持 OptiFine, Fabric 与 Forge 同时安装在 Minecraft 1.13 及以上版本
install.failed.version_mismatch=该软件需要的游戏版本为 %s但实际的游戏版本为 %s。
install.installer.choose=选择 %s 版本
install.installer.fabric=Fabric
install.installer.forge=Forge
install.installer.game=游戏
install.installer.install=安装 %s

View File

@@ -17,6 +17,7 @@
*/
package org.jackhuang.hmcl.download;
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.liteloader.LiteLoaderBMCLVersionList;
@@ -43,6 +44,8 @@ public class BMCLAPIDownloadProvider implements DownloadProvider {
switch (id) {
case "game":
return GameVersionList.INSTANCE;
case "fabric":
return FabricVersionList.INSTANCE;
case "forge":
return ForgeBMCLVersionList.INSTANCE;
case "liteloader":

View File

@@ -18,14 +18,10 @@
package org.jackhuang.hmcl.download;
import org.jackhuang.hmcl.download.forge.ForgeInstallTask;
import org.jackhuang.hmcl.download.forge.ForgeRemoteVersion;
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
import org.jackhuang.hmcl.download.game.GameDownloadTask;
import org.jackhuang.hmcl.download.game.GameLibrariesTask;
import org.jackhuang.hmcl.download.liteloader.LiteLoaderInstallTask;
import org.jackhuang.hmcl.download.liteloader.LiteLoaderRemoteVersion;
import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask;
import org.jackhuang.hmcl.download.optifine.OptiFineRemoteVersion;
import org.jackhuang.hmcl.game.DefaultGameRepository;
import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.game.Version;
@@ -93,30 +89,21 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
}
@Override
public Task<Version> installLibraryAsync(String gameVersion, Version version, String libraryId, String libraryVersion) {
if (version.isResolved()) throw new IllegalArgumentException("Version should not be resolved");
public Task<Version> installLibraryAsync(String gameVersion, Version baseVersion, String libraryId, String libraryVersion) {
if (baseVersion.isResolved()) throw new IllegalArgumentException("Version should not be resolved");
VersionList<?> versionList = getVersionList(libraryId);
return versionList.loadAsync(gameVersion, getDownloadProvider())
.thenComposeAsync(() -> installLibraryAsync(version, versionList.getVersion(gameVersion, libraryVersion)
.thenComposeAsync(() -> installLibraryAsync(baseVersion, versionList.getVersion(gameVersion, libraryVersion)
.orElseThrow(() -> new IllegalStateException("Remote library " + libraryId + " has no version " + libraryVersion))));
}
@Override
public Task<Version> installLibraryAsync(Version oldVersion, RemoteVersion libraryVersion) {
if (oldVersion.isResolved()) throw new IllegalArgumentException("Version should not be resolved");
public Task<Version> installLibraryAsync(Version baseVersion, RemoteVersion libraryVersion) {
if (baseVersion.isResolved()) throw new IllegalArgumentException("Version should not be resolved");
Task<Version> task;
if (libraryVersion instanceof ForgeRemoteVersion)
task = new ForgeInstallTask(this, oldVersion, (ForgeRemoteVersion) libraryVersion);
else if (libraryVersion instanceof LiteLoaderRemoteVersion)
task = new LiteLoaderInstallTask(this, oldVersion, (LiteLoaderRemoteVersion) libraryVersion);
else if (libraryVersion instanceof OptiFineRemoteVersion)
task = new OptiFineInstallTask(this, oldVersion, (OptiFineRemoteVersion) libraryVersion);
else
throw new IllegalArgumentException("Remote library " + libraryVersion + " is unrecognized.");
return task
.thenApplyAsync(oldVersion::addPatch)
return libraryVersion.getInstallTask(this, baseVersion)
.thenApplyAsync(baseVersion::addPatch)
.thenComposeAsync(repository::save);
}
@@ -163,19 +150,19 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
switch (libraryId) {
case "forge":
analyzer.ifPresent(LibraryAnalyzer.LibraryType.FORGE, (library, libraryVersion) -> newList.remove(library));
version = version.removePatchById("net.minecraftforge");
version = version.removePatchById(LibraryAnalyzer.LibraryType.FORGE.getPatchId());
break;
case "liteloader":
analyzer.ifPresent(LibraryAnalyzer.LibraryType.LITELOADER, (library, libraryVersion) -> newList.remove(library));
version = version.removePatchById("com.mumfrey.liteloader");
version = version.removePatchById(LibraryAnalyzer.LibraryType.LITELOADER.getPatchId());
break;
case "optifine":
analyzer.ifPresent(LibraryAnalyzer.LibraryType.OPTIFINE, (library, libraryVersion) -> newList.remove(library));
version = version.removePatchById("net.optifine");
version = version.removePatchById(LibraryAnalyzer.LibraryType.OPTIFINE.getPatchId());
break;
case "fabric":
analyzer.ifPresent(LibraryAnalyzer.LibraryType.FABRIC, (library, libraryVersion) -> newList.remove(library));
version = version.removePatchById("net.fabricmc");
version = version.removePatchById(LibraryAnalyzer.LibraryType.FABRIC.getPatchId());
break;
}
return new MaintainTask(version.setLibraries(newList));

View File

@@ -26,6 +26,8 @@ import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import java.util.Map;
/**
*
* @author huangyuhui
@@ -60,12 +62,8 @@ public class DefaultGameBuilder extends GameBuilder {
Task<Version> libraryTask = vanillaTask.thenSupplyAsync(() -> version);
if (toolVersions.containsKey("forge"))
libraryTask = libraryTask.thenComposeAsync(libraryTaskHelper(gameVersion, "forge"));
if (toolVersions.containsKey("liteloader"))
libraryTask = libraryTask.thenComposeAsync(libraryTaskHelper(gameVersion, "liteloader"));
if (toolVersions.containsKey("optifine"))
libraryTask = libraryTask.thenComposeAsync(libraryTaskHelper(gameVersion, "optifine"));
for (Map.Entry<String, String> entry : toolVersions.entrySet())
libraryTask = libraryTask.thenComposeAsync(libraryTaskHelper(gameVersion, entry.getKey(), entry.getValue()));
for (RemoteVersion remoteVersion : remoteVersions)
libraryTask = libraryTask.thenComposeAsync(dependencyManager.installLibraryAsync(remoteVersion));
@@ -77,8 +75,8 @@ public class DefaultGameBuilder extends GameBuilder {
});
}
private ExceptionalFunction<Version, Task<Version>, ?> libraryTaskHelper(String gameVersion, String libraryId) {
return version -> dependencyManager.installLibraryAsync(gameVersion, version, libraryId, toolVersions.get(libraryId));
private ExceptionalFunction<Version, Task<Version>, ?> libraryTaskHelper(String gameVersion, String libraryId, String libraryVersion) {
return version -> dependencyManager.installLibraryAsync(gameVersion, version, libraryId, libraryVersion);
}
protected Task<Void> downloadGameAsync(String gameVersion, Version version) {

View File

@@ -66,22 +66,22 @@ public interface DependencyManager {
* **Note**: Installing a library may change the version.json.
*
* @param gameVersion the Minecraft version that the library relies on.
* @param version the version.json.
* @param baseVersion the version.json.
* @param libraryId the type of being installed library. i.e. "forge", "liteloader", "optifine"
* @param libraryVersion the version of being installed library.
* @return the task to install the specific library.
*/
Task<?> installLibraryAsync(String gameVersion, Version version, String libraryId, String libraryVersion);
Task<?> installLibraryAsync(String gameVersion, Version baseVersion, String libraryId, String libraryVersion);
/**
* Install a library to a version.
* **Note**: Installing a library may change the version.json.
*
* @param version the version.json.\
* @param baseVersion the version.json.
* @param libraryVersion the remote version of being installed library.
* @return the task to install the specific library.
*/
Task<?> installLibraryAsync(Version version, RemoteVersion libraryVersion);
Task<?> installLibraryAsync(Version baseVersion, RemoteVersion libraryVersion);
/**
* Get registered version list.

View File

@@ -84,10 +84,10 @@ public final class LibraryAnalyzer {
}
public enum LibraryType {
FORGE(true, "net.minecraftforge", Pattern.compile("net\\.minecraftforge"), Pattern.compile("forge")),
LITELOADER(true, "com.mumfrey.liteloader", Pattern.compile("com\\.mumfrey"), Pattern.compile("liteloader")),
OPTIFINE(false, "net.optifine", Pattern.compile("(net\\.)?optifine"), Pattern.compile(".*")),
FABRIC(true, "net.fabricmc", Pattern.compile("net\\.fabricmc"), Pattern.compile("fabric-loader"));
FABRIC(true, "fabric", Pattern.compile("net\\.fabricmc"), Pattern.compile("fabric-loader")),
FORGE(true, "forge", Pattern.compile("net\\.minecraftforge"), Pattern.compile("forge")),
LITELOADER(true, "liteloader", Pattern.compile("com\\.mumfrey"), Pattern.compile("liteloader")),
OPTIFINE(false, "optifine", Pattern.compile("(net\\.)?optifine"), Pattern.compile("^(?!.*launchwrapper).*$"));
private final boolean modLoader;
private final String patchId;
@@ -103,5 +103,9 @@ public final class LibraryAnalyzer {
public boolean isModLoader() {
return modLoader;
}
public String getPatchId() {
return patchId;
}
}
}

View File

@@ -40,6 +40,7 @@ public class MaintainTask extends Task<Version> {
} else {
// Vanilla Minecraft does not need maintain
// Forge 1.13 support not implemented, not compatible with OptiFine currently.
// Fabric does not need maintain, nothing compatible with fabric now.
return version;
}
}

View File

@@ -17,6 +17,7 @@
*/
package org.jackhuang.hmcl.download;
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.liteloader.LiteLoaderVersionList;
@@ -43,6 +44,8 @@ public class MojangDownloadProvider implements DownloadProvider {
switch (id) {
case "game":
return GameVersionList.INSTANCE;
case "fabric":
return FabricVersionList.INSTANCE;
case "forge":
return ForgeBMCLVersionList.INSTANCE;
case "liteloader":

View File

@@ -17,6 +17,8 @@
*/
package org.jackhuang.hmcl.download;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.ToStringBuilder;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
@@ -75,6 +77,10 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
return type;
}
public Task<Version> getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) {
throw new UnsupportedOperationException(toString() + " cannot be installed yet");
}
@Override
public boolean equals(Object obj) {
return obj instanceof RemoteVersion && Objects.equals(selfVersion, ((RemoteVersion) obj).selfVersion);

View File

@@ -0,0 +1,132 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 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.fabric;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.game.Arguments;
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 org.jackhuang.hmcl.util.io.NetworkUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
/**
* <b>Note</b>: Fabric should be installed first.
*
* @author huangyuhui
*/
public final class FabricInstallTask extends Task<Version> {
private final DefaultDependencyManager dependencyManager;
private final Version version;
private final FabricRemoteVersion remote;
private final GetTask launchMetaTask;
private final List<Task<?>> dependencies = new LinkedList<>();
public FabricInstallTask(DefaultDependencyManager dependencyManager, Version version, FabricRemoteVersion remoteVersion) {
this.dependencyManager = dependencyManager;
this.version = version;
this.remote = remoteVersion;
launchMetaTask = new GetTask(NetworkUtils.toURL(getLaunchMetaUrl(remote.getSelfVersion())))
.setCacheRepository(dependencyManager.getCacheRepository());
}
@Override
public boolean doPreExecute() {
return true;
}
@Override
public void preExecute() {
if (!Objects.equals("net.minecraft.client.main.Main", version.getMainClass()))
throw new UnsupportedFabricInstallationException();
}
@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() throws IOException {
setResult(getPatch(JsonUtils.GSON.fromJson(launchMetaTask.getResult(), JsonObject.class), remote.getGameVersion(), remote.getSelfVersion()));
dependencies.add(dependencyManager.checkLibraryCompletionAsync(getResult()));
}
private static String getLaunchMetaUrl(String loaderVersion) {
return String.format("%s/%s/%s/%s/%3$s-%4$s.json", "https://maven.fabricmc.net/", "net/fabricmc", "fabric-loader", loaderVersion);
}
private Version getPatch(JsonObject jsonObject, String gameVersion, String loaderVersion) {
Arguments arguments = new Arguments();
String mainClass;
if (!jsonObject.get("mainClass").isJsonObject()) {
mainClass = jsonObject.get("mainClass").getAsString();
} else {
mainClass = jsonObject.get("mainClass").getAsJsonObject().get("client").getAsString();
}
if (jsonObject.has("launchwrapper")) {
String clientTweaker = jsonObject.get("launchwrapper").getAsJsonObject().get("tweakers").getAsJsonObject().get("client").getAsJsonArray().get(0).getAsString();
arguments = arguments.addGameArguments("--tweakClass", clientTweaker);
}
JsonObject librariesObject = jsonObject.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("net.fabricmc", "intermediary", gameVersion, null, "https://maven.fabricmc.net/", null));
libraries.add(new Library("net.fabricmc", "fabric-loader", loaderVersion, null, "https://maven.fabricmc.net/", null));
return new Version("net.fabricmc", loaderVersion, 30000, arguments, mainClass, libraries);
}
public static class UnsupportedFabricInstallationException extends UnsupportedOperationException {
}
}

View File

@@ -0,0 +1,41 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 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.fabric;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.Task;
public class FabricRemoteVersion extends RemoteVersion {
/**
* Constructor.
*
* @param gameVersion the Minecraft version that this remote version suits.
* @param selfVersion the version string of the remote version.
* @param url the installer or universal jar URL.
*/
FabricRemoteVersion(String gameVersion, String selfVersion, String url) {
super(gameVersion, selfVersion, url);
}
@Override
public Task<Version> getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) {
return new FabricInstallTask(dependencyManager, baseVersion, this);
}
}

View File

@@ -0,0 +1,119 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 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.fabric;
import com.google.gson.reflect.TypeToken;
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 javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public final class FabricVersionList extends VersionList<FabricRemoteVersion> {
public static final FabricVersionList INSTANCE = new FabricVersionList();
private FabricVersionList() {
}
@Override
public boolean hasType() {
return false;
}
@Override
public Task<?> refreshAsync(DownloadProvider downloadProvider) {
return new Task<Void>() {
@Override
public void execute() throws IOException, XMLStreamException {
List<String> gameVersions = getGameVersions(META_URL);
List<String> loaderVersions = getVersions(FABRIC_MAVEN_URL, FABRIC_PACKAGE_NAME, FABRIC_JAR_NAME);
lock.writeLock().lock();
try {
for (String gameVersion : gameVersions)
for (String loaderVersion : loaderVersions)
versions.put(gameVersion, new FabricRemoteVersion(gameVersion, loaderVersion, ""));
} finally {
lock.writeLock().unlock();
}
}
};
}
private static final String META_URL = "https://meta.fabricmc.net/v2/versions/game";
private static final String FABRIC_MAVEN_URL = "https://maven.fabricmc.net/";
private static final String FABRIC_PACKAGE_NAME = "net/fabricmc";
private static final String FABRIC_JAR_NAME = "fabric-loader";
private List<String> getVersions(String mavenServerURL, String packageName, String jarName) throws IOException, XMLStreamException {
List<String> versions = new ArrayList<>();
URL url = new URL(mavenServerURL + packageName + "/" + jarName + "/maven-metadata.xml");
XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(url.openStream());
while(reader.hasNext()) {
if (reader.next() == 1 && reader.getLocalName().equals("version")) {
String text = reader.getElementText();
versions.add(text);
}
}
reader.close();
Collections.reverse(versions);
return versions;
}
private List<String> getGameVersions(String metaUrl) throws IOException {
String json = NetworkUtils.doGet(NetworkUtils.toURL(metaUrl));
return JsonUtils.GSON.<ArrayList<GameVersion>>fromJson(json, new TypeToken<ArrayList<GameVersion>>() {
}.getType()).stream().map(GameVersion::getVersion).collect(Collectors.toList());
}
private static class GameVersion {
private final String version;
private final boolean stable;
public GameVersion() {
this("", false);
}
public GameVersion(String version, boolean stable) {
this.version = version;
this.stable = stable;
}
public String getVersion() {
return version;
}
public boolean isStable() {
return stable;
}
}
}

View File

@@ -18,6 +18,7 @@
package org.jackhuang.hmcl.download.forge;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.LibraryAnalyzer;
import org.jackhuang.hmcl.download.VersionMismatchException;
import org.jackhuang.hmcl.game.GameVersion;
import org.jackhuang.hmcl.game.Version;
@@ -80,8 +81,8 @@ public final class ForgeInstallTask extends Task<Version> {
public void postExecute() throws Exception {
Files.deleteIfExists(installer);
setResult(dependency.getResult()
.setPriority(10000)
.setId("net.minecraftforge")
.setPriority(30000)
.setId(LibraryAnalyzer.LibraryType.FORGE.getPatchId())
.setVersion(remote.getSelfVersion()));
}

View File

@@ -134,7 +134,7 @@ public class ForgeNewInstallTask extends Task<Version> {
@Override
public void execute() throws Exception {
if ("net.minecraft.launchwrapper.Launch".equals(version.getMainClass()))
if ("net.minecraft.launchwrapper.Launch".equals(version.resolve(dependencyManager.getGameRepository()).getMainClass()))
throw new OptiFineInstallTask.UnsupportedOptiFineInstallationException();
Path temp = Files.createTempDirectory("forge_installer");

View File

@@ -17,7 +17,10 @@
*/
package org.jackhuang.hmcl.download.forge;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.Task;
public class ForgeRemoteVersion extends RemoteVersion {
/**
@@ -30,4 +33,9 @@ public class ForgeRemoteVersion extends RemoteVersion {
public ForgeRemoteVersion(String gameVersion, String selfVersion, String url) {
super(gameVersion, selfVersion, url);
}
@Override
public Task<Version> getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) {
return new ForgeInstallTask(dependencyManager, baseVersion, this);
}
}

View File

@@ -18,6 +18,7 @@
package org.jackhuang.hmcl.download.liteloader;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.LibraryAnalyzer;
import org.jackhuang.hmcl.game.Arguments;
import org.jackhuang.hmcl.game.LibrariesDownloadInfo;
import org.jackhuang.hmcl.game.Library;
@@ -68,9 +69,9 @@ public final class LiteLoaderInstallTask extends Task<Version> {
new LibrariesDownloadInfo(new LibraryDownloadInfo(null, remote.getUrl()))
);
setResult(new Version("com.mumfrey.liteloader",
setResult(new Version(LibraryAnalyzer.LibraryType.LITELOADER.getPatchId(),
remote.getSelfVersion(),
20000,
60000,
new Arguments().addGameArguments("--tweakClass", "com.mumfrey.liteloader.launch.LiteLoaderTweaker"),
"net.minecraft.launchwrapper.Launch",
Lang.merge(remote.getLibraries(), Collections.singleton(library)))

View File

@@ -17,8 +17,11 @@
*/
package org.jackhuang.hmcl.download.liteloader;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.Task;
import java.util.Collection;
@@ -47,4 +50,8 @@ public class LiteLoaderRemoteVersion extends RemoteVersion {
return tweakClass;
}
@Override
public Task<Version> getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) {
return new LiteLoaderInstallTask(dependencyManager, baseVersion, this);
}
}

View File

@@ -18,6 +18,7 @@
package org.jackhuang.hmcl.download.optifine;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.LibraryAnalyzer;
import org.jackhuang.hmcl.download.VersionMismatchException;
import org.jackhuang.hmcl.game.Arguments;
import org.jackhuang.hmcl.game.GameVersion;
@@ -97,7 +98,7 @@ public final class OptiFineInstallTask extends Task<Version> {
public void preExecute() throws Exception {
if (!Arrays.asList("net.minecraft.client.main.Main",
"net.minecraft.launchwrapper.Launch")
.contains(version.getMainClass()))
.contains(version.resolve(dependencyManager.getGameRepository()).getMainClass()))
throw new UnsupportedOptiFineInstallationException();
@@ -156,9 +157,9 @@ public final class OptiFineInstallTask extends Task<Version> {
}
setResult(new Version(
"net.optifine",
LibraryAnalyzer.LibraryType.OPTIFINE.getPatchId(),
remote.getSelfVersion(),
30000,
90000,
new Arguments().addGameArguments("--tweakClass", "optifine.OptiFineTweaker"),
"net.minecraft.launchwrapper.Launch",
libraries

View File

@@ -17,7 +17,10 @@
*/
package org.jackhuang.hmcl.download.optifine;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.Task;
import java.util.function.Supplier;
@@ -34,4 +37,9 @@ public class OptiFineRemoteVersion extends RemoteVersion {
public String getUrl() {
return url.get();
}
@Override
public Task<Version> getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) {
return new OptiFineInstallTask(dependencyManager, baseVersion, this);
}
}

View File

@@ -65,17 +65,17 @@ public class Version implements Comparable<Version>, Validation {
private final ReleaseType type;
private final Date time;
private final Date releaseTime;
private final int minimumLauncherVersion;
private final Integer minimumLauncherVersion;
private final Boolean hidden;
private final List<Version> patches;
private transient final boolean resolved;
public Version(String id, String version, int priority, Arguments arguments, String mainClass, List<Library> libraries) {
this(false, id, version, priority, null, arguments, mainClass, null, null, null, null, libraries, null, null, null, null, null, null, 0, null, null);
this(false, id, version, priority, null, arguments, mainClass, null, null, null, null, libraries, null, null, null, null, null, null, null, null, null);
}
public Version(boolean resolved, String id, String version, Integer priority, String minecraftArguments, Arguments arguments, String mainClass, String inheritsFrom, String jar, AssetIndexInfo assetIndex, String assets, List<Library> libraries, List<CompatibilityRule> compatibilityRules, Map<DownloadType, DownloadInfo> downloads, Map<DownloadType, LoggingInfo> logging, ReleaseType type, Date time, Date releaseTime, int minimumLauncherVersion, Boolean hidden, List<Version> patches) {
public Version(boolean resolved, String id, String version, Integer priority, String minecraftArguments, Arguments arguments, String mainClass, String inheritsFrom, String jar, AssetIndexInfo assetIndex, String assets, List<Library> libraries, List<CompatibilityRule> compatibilityRules, Map<DownloadType, DownloadInfo> downloads, Map<DownloadType, LoggingInfo> logging, ReleaseType type, Date time, Date releaseTime, Integer minimumLauncherVersion, Boolean hidden, List<Version> patches) {
this.resolved = resolved;
this.id = id;
this.version = version;
@@ -150,7 +150,7 @@ public class Version implements Comparable<Version>, Validation {
}
public int getMinimumLauncherVersion() {
return minimumLauncherVersion;
return minimumLauncherVersion == null ? 0 : minimumLauncherVersion;
}
public boolean isHidden() {
@@ -222,7 +222,7 @@ public class Version implements Comparable<Version>, Validation {
type,
time,
releaseTime,
Math.max(minimumLauncherVersion, parent.minimumLauncherVersion),
Lang.merge(minimumLauncherVersion, parent.minimumLauncherVersion, Math::max),
hidden,
Lang.merge(parent.patches, patches));
}

View File

@@ -24,6 +24,7 @@ import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -95,6 +96,12 @@ public final class Lang {
return index < 0 || index >= a.size() ? defaultValue : a.get(index);
}
public static <T> T merge(T a, T b, BinaryOperator<T> operator) {
if (a == null) return b;
if (b == null) return a;
return operator.apply(a, b);
}
/**
* Join two collections into one list.
*