Compare commits
24
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91a613d966 | ||
|
|
06b7b1e574 | ||
|
|
933b7cab2f | ||
|
|
885e25d07a | ||
|
|
421f9a3b2b | ||
|
|
e52cfb036e | ||
|
|
87a3a4ece3 | ||
|
|
b152e2bfab | ||
|
|
c9c97a45b7 | ||
|
|
cba4ba2799 | ||
|
|
ab9eea3620 | ||
|
|
96f2d011ca | ||
|
|
9ee55cd17d | ||
|
|
07ee7867da | ||
|
|
940d4f722c | ||
|
|
af56096162 | ||
|
|
0e12c1a132 | ||
|
|
ca20509cb1 | ||
|
|
58c93ff349 | ||
|
|
aab3b22290 | ||
|
|
6cf933741f | ||
|
|
9d17834227 | ||
|
|
7805508d4c | ||
|
|
72f6c2faf3 |
No files matched your search
+73
-37
@@ -108,6 +108,50 @@ tasks.compileJava {
|
||||
options.compilerArgs.add("--add-exports=java.base/jdk.internal.loader=ALL-UNNAMED")
|
||||
}
|
||||
|
||||
val hmclProperties = buildList {
|
||||
add("hmcl.version" to project.version.toString())
|
||||
System.getenv("GITHUB_SHA")?.let {
|
||||
add("hmcl.version.hash" to it)
|
||||
}
|
||||
add("hmcl.version.type" to versionType)
|
||||
add("hmcl.microsoft.auth.id" to microsoftAuthId)
|
||||
add("hmcl.microsoft.auth.secret" to microsoftAuthSecret)
|
||||
add("hmcl.curseforge.apikey" to curseForgeApiKey)
|
||||
add("hmcl.authlib-injector.version" to libs.authlib.injector.get().version!!)
|
||||
}
|
||||
|
||||
val hmclPropertiesFile = layout.buildDirectory.file("hmcl.properties")
|
||||
val createPropertiesFile by tasks.registering {
|
||||
outputs.file(hmclPropertiesFile)
|
||||
hmclProperties.forEach { (k, v) -> inputs.property(k, v) }
|
||||
|
||||
doLast {
|
||||
val targetFile = hmclPropertiesFile.get().asFile
|
||||
targetFile.parentFile.mkdir()
|
||||
targetFile.bufferedWriter().use {
|
||||
for ((k, v) in hmclProperties) {
|
||||
it.write("$k=$v\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val addOpens = listOf(
|
||||
"java.base/java.lang",
|
||||
"java.base/java.lang.reflect",
|
||||
"java.base/jdk.internal.loader",
|
||||
"javafx.base/com.sun.javafx.binding",
|
||||
"javafx.base/com.sun.javafx.event",
|
||||
"javafx.base/com.sun.javafx.runtime",
|
||||
"javafx.graphics/javafx.css",
|
||||
"javafx.graphics/com.sun.javafx.stage",
|
||||
"javafx.graphics/com.sun.prism",
|
||||
"javafx.controls/com.sun.javafx.scene.control",
|
||||
"javafx.controls/com.sun.javafx.scene.control.behavior",
|
||||
"javafx.controls/javafx.scene.control.skin",
|
||||
"jdk.attach/sun.tools.attach",
|
||||
)
|
||||
|
||||
tasks.jar {
|
||||
enabled = false
|
||||
dependsOn(tasks["shadowJar"])
|
||||
@@ -116,6 +160,8 @@ tasks.jar {
|
||||
val jarPath = tasks.jar.get().archiveFile.get().asFile
|
||||
|
||||
tasks.shadowJar {
|
||||
dependsOn(createPropertiesFile)
|
||||
|
||||
archiveClassifier.set(null as String?)
|
||||
|
||||
exclude("**/package-info.class")
|
||||
@@ -136,43 +182,14 @@ tasks.shadowJar {
|
||||
exclude(project(":HMCLBoot"))
|
||||
}
|
||||
|
||||
into("assets/") {
|
||||
from(embedResources)
|
||||
}
|
||||
|
||||
manifest {
|
||||
attributes(
|
||||
"Created-By" to "Copyright(c) 2013-2025 huangyuhui.",
|
||||
"Main-Class" to "org.jackhuang.hmcl.Main",
|
||||
"Multi-Release" to "true",
|
||||
"Implementation-Version" to project.version,
|
||||
"Microsoft-Auth-Id" to microsoftAuthId,
|
||||
"Microsoft-Auth-Secret" to microsoftAuthSecret,
|
||||
"CurseForge-Api-Key" to curseForgeApiKey,
|
||||
"Authlib-Injector-Version" to libs.authlib.injector.get().version!!,
|
||||
"Build-Channel" to versionType,
|
||||
"Add-Opens" to listOf(
|
||||
"java.base/java.lang",
|
||||
"java.base/java.lang.reflect",
|
||||
"java.base/jdk.internal.loader",
|
||||
"javafx.base/com.sun.javafx.binding",
|
||||
"javafx.base/com.sun.javafx.event",
|
||||
"javafx.base/com.sun.javafx.runtime",
|
||||
"javafx.graphics/javafx.css",
|
||||
"javafx.graphics/com.sun.javafx.stage",
|
||||
"javafx.graphics/com.sun.prism",
|
||||
"javafx.controls/com.sun.javafx.scene.control",
|
||||
"javafx.controls/com.sun.javafx.scene.control.behavior",
|
||||
"javafx.controls/javafx.scene.control.skin",
|
||||
"jdk.attach/sun.tools.attach",
|
||||
).joinToString(" "),
|
||||
"Enable-Native-Access" to "ALL-UNNAMED"
|
||||
)
|
||||
|
||||
System.getenv("GITHUB_SHA")?.also {
|
||||
attributes("GitHub-SHA" to it)
|
||||
}
|
||||
}
|
||||
manifest.attributes(
|
||||
"Created-By" to "Copyright(c) 2013-2025 huangyuhui.",
|
||||
"Implementation-Version" to project.version.toString(),
|
||||
"Main-Class" to "org.jackhuang.hmcl.Main",
|
||||
"Multi-Release" to "true",
|
||||
"Add-Opens" to addOpens.joinToString(" "),
|
||||
"Enable-Native-Access" to "ALL-UNNAMED"
|
||||
)
|
||||
|
||||
if (launcherExe != null) {
|
||||
into("assets") {
|
||||
@@ -186,6 +203,15 @@ tasks.shadowJar {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.processResources {
|
||||
dependsOn(createPropertiesFile)
|
||||
|
||||
into("assets/") {
|
||||
from(hmclPropertiesFile)
|
||||
from(embedResources)
|
||||
}
|
||||
}
|
||||
|
||||
val makeExecutables by tasks.registering {
|
||||
val extensions = listOf("exe", "sh")
|
||||
|
||||
@@ -268,6 +294,16 @@ fun parseToolOptions(options: String?): MutableList<String> {
|
||||
return result
|
||||
}
|
||||
|
||||
// For IntelliJ IDEA
|
||||
tasks.withType<JavaExec> {
|
||||
if (name != "run") {
|
||||
jvmArgs(addOpens.map { "--add-opens=$it=ALL-UNNAMED" })
|
||||
if (javaVersion >= JavaVersion.VERSION_24) {
|
||||
jvmArgs("--enable-native-access=ALL-UNNAMED")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register<JavaExec>("run") {
|
||||
dependsOn(tasks.jar)
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ public final class Metadata {
|
||||
|
||||
public static final String NAME = "HMCL";
|
||||
public static final String FULL_NAME = "Hello Minecraft! Launcher";
|
||||
public static final String VERSION = System.getProperty("hmcl.version.override", JarUtils.getManifestAttribute("Implementation-Version", "@develop@"));
|
||||
public static final String VERSION = System.getProperty("hmcl.version.override", JarUtils.getAttribute("hmcl.version", "@develop@"));
|
||||
|
||||
public static final String TITLE = NAME + " " + VERSION;
|
||||
public static final String FULL_TITLE = FULL_NAME + " v" + VERSION;
|
||||
@@ -49,8 +49,8 @@ public final class Metadata {
|
||||
public static final String EULA_URL = DOCS_URL + "/eula/hmcl.html";
|
||||
public static final String GROUPS_URL = "https://www.bilibili.com/opus/905435541874409529";
|
||||
|
||||
public static final String BUILD_CHANNEL = JarUtils.getManifestAttribute("Build-Channel", "nightly");
|
||||
public static final String GITHUB_SHA = JarUtils.getManifestAttribute("GitHub-SHA", null);
|
||||
public static final String BUILD_CHANNEL = JarUtils.getAttribute("hmcl.version.type", "nightly");
|
||||
public static final String GITHUB_SHA = JarUtils.getAttribute("hmcl.version.hash", null);
|
||||
|
||||
public static final Path CURRENT_DIRECTORY = Paths.get(System.getProperty("user.dir")).toAbsolutePath().normalize();
|
||||
public static final Path MINECRAFT_DIRECTORY = OperatingSystem.getWorkingDirectory("minecraft");
|
||||
|
||||
@@ -50,10 +50,7 @@ import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
@@ -210,14 +207,17 @@ public final class ModpackHelper {
|
||||
if (modpack.getManifest() instanceof MultiMCInstanceConfiguration)
|
||||
return modpack.getInstallTask(profile.getDependency(), zipFile, name)
|
||||
.whenComplete(Schedulers.defaultScheduler(), success, failure)
|
||||
.thenComposeAsync(createMultiMCPostInstallTask(profile, (MultiMCInstanceConfiguration) modpack.getManifest(), name));
|
||||
.thenComposeAsync(createMultiMCPostInstallTask(profile, (MultiMCInstanceConfiguration) modpack.getManifest(), name))
|
||||
.withStagesHint(List.of("hmcl.modpack", "hmcl.modpack.download"));
|
||||
else if (modpack.getManifest() instanceof McbbsModpackManifest)
|
||||
return modpack.getInstallTask(profile.getDependency(), zipFile, name)
|
||||
.whenComplete(Schedulers.defaultScheduler(), success, failure)
|
||||
.thenComposeAsync(createMcbbsPostInstallTask(profile, (McbbsModpackManifest) modpack.getManifest(), name));
|
||||
.thenComposeAsync(createMcbbsPostInstallTask(profile, (McbbsModpackManifest) modpack.getManifest(), name))
|
||||
.withStagesHint(List.of("hmcl.modpack", "hmcl.modpack.download"));
|
||||
else
|
||||
return modpack.getInstallTask(profile.getDependency(), zipFile, name)
|
||||
.whenComplete(Schedulers.javafx(), success, failure);
|
||||
.whenComplete(Schedulers.javafx(), success, failure)
|
||||
.withStagesHint(List.of("hmcl.modpack", "hmcl.modpack.download"));
|
||||
}
|
||||
|
||||
public static Task<Void> getUpdateTask(Profile profile, ServerModpackManifest manifest, Charset charset, String name, ModpackConfiguration<?> configuration) throws UnsupportedModpackException {
|
||||
|
||||
@@ -158,13 +158,13 @@ public final class OAuthServer extends NanoHTTPD implements OAuth.Session {
|
||||
@Override
|
||||
public String getClientId() {
|
||||
return System.getProperty("hmcl.microsoft.auth.id",
|
||||
JarUtils.getManifestAttribute("Microsoft-Auth-Id", ""));
|
||||
JarUtils.getAttribute("hmcl.microsoft.auth.id", ""));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientSecret() {
|
||||
return System.getProperty("hmcl.microsoft.auth.secret",
|
||||
JarUtils.getManifestAttribute("Microsoft-Auth-Secret", ""));
|
||||
JarUtils.getAttribute("hmcl.microsoft.auth.secret", ""));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -363,9 +363,9 @@ public final class Accounts {
|
||||
return new SimpleAuthlibInjectorArtifactProvider(Paths.get(authlibinjectorLocation));
|
||||
}
|
||||
|
||||
String authlibInjectorVersion = JarUtils.getManifestAttribute("Authlib-Injector-Version", null);
|
||||
String authlibInjectorVersion = JarUtils.getAttribute("hmcl.authlib-injector.version", null);
|
||||
if (authlibInjectorVersion == null)
|
||||
throw new AssertionError("Missing Authlib-Injector-Version");
|
||||
throw new AssertionError("Missing hmcl.authlib-injector.version");
|
||||
|
||||
String authlibInjectorFileName = "authlib-injector-" + authlibInjectorVersion + ".jar";
|
||||
return new AuthlibInjectorExtractor(Accounts.class.getResource("/assets/" + authlibInjectorFileName),
|
||||
|
||||
@@ -29,12 +29,12 @@ public enum VersionIconType {
|
||||
COMMAND("/assets/img/command.png"),
|
||||
OPTIFINE("/assets/img/optifine.png"),
|
||||
CRAFT_TABLE("/assets/img/craft_table.png"),
|
||||
APRIL_FOOLS("/assets/img/april_fools.png"),
|
||||
FABRIC("/assets/img/fabric.png"),
|
||||
FORGE("/assets/img/forge.png"),
|
||||
NEO_FORGE("/assets/img/neoforge.png"),
|
||||
FURNACE("/assets/img/furnace.png"),
|
||||
QUILT("/assets/img/quilt.png");
|
||||
QUILT("/assets/img/quilt.png"),
|
||||
APRIL_FOOLS("/assets/img/april_fools.png");
|
||||
|
||||
// Please append new items at last
|
||||
|
||||
|
||||
@@ -797,14 +797,6 @@ public final class VersionSetting implements Cloneable, Observable {
|
||||
return obj;
|
||||
}
|
||||
|
||||
private static <T> T getOrDefault(T[] values, JsonElement index, T defaultValue) {
|
||||
if (index == null)
|
||||
return defaultValue;
|
||||
|
||||
int idx = index.getAsInt();
|
||||
return idx >= 0 && idx < values.length ? values[idx] : defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VersionSetting deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
if (!(json instanceof JsonObject))
|
||||
@@ -839,14 +831,14 @@ public final class VersionSetting implements Cloneable, Observable {
|
||||
vs.setNotCheckJVM(Optional.ofNullable(obj.get("notCheckJVM")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setNotPatchNatives(Optional.ofNullable(obj.get("notPatchNatives")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setShowLogs(Optional.ofNullable(obj.get("showLogs")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setLauncherVisibility(getOrDefault(LauncherVisibility.values(), obj.get("launcherVisibility"), LauncherVisibility.HIDE));
|
||||
vs.setProcessPriority(getOrDefault(ProcessPriority.values(), obj.get("processPriority"), ProcessPriority.NORMAL));
|
||||
vs.setLauncherVisibility(parseJsonPrimitive(obj.getAsJsonPrimitive("launcherVisibility"), LauncherVisibility.class, LauncherVisibility.HIDE));
|
||||
vs.setProcessPriority(parseJsonPrimitive(obj.getAsJsonPrimitive("processPriority"), ProcessPriority.class, ProcessPriority.NORMAL));
|
||||
vs.setUseNativeGLFW(Optional.ofNullable(obj.get("useNativeGLFW")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setUseNativeOpenAL(Optional.ofNullable(obj.get("useNativeOpenAL")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setGameDirType(getOrDefault(GameDirectoryType.values(), obj.get("gameDirType"), GameDirectoryType.ROOT_FOLDER));
|
||||
vs.setGameDirType(parseJsonPrimitive(obj.getAsJsonPrimitive("gameDirType"), GameDirectoryType.class, GameDirectoryType.ROOT_FOLDER));
|
||||
vs.setDefaultJavaPath(Optional.ofNullable(obj.get("defaultJavaPath")).map(JsonElement::getAsString).orElse(null));
|
||||
vs.setNativesDirType(getOrDefault(NativesDirectoryType.values(), obj.get("nativesDirType"), NativesDirectoryType.VERSION_FOLDER));
|
||||
vs.setVersionIcon(getOrDefault(VersionIconType.values(), obj.get("versionIcon"), VersionIconType.DEFAULT));
|
||||
vs.setNativesDirType(parseJsonPrimitive(obj.getAsJsonPrimitive("nativesDirType"), NativesDirectoryType.class, NativesDirectoryType.VERSION_FOLDER));
|
||||
vs.setVersionIcon(parseJsonPrimitive(obj.getAsJsonPrimitive("versionIcon"), VersionIconType.class, VersionIconType.DEFAULT));
|
||||
|
||||
if (obj.get("javaVersionType") != null) {
|
||||
JavaVersionType javaVersionType = parseJsonPrimitive(obj.getAsJsonPrimitive("javaVersionType"), JavaVersionType.class, JavaVersionType.AUTO);
|
||||
|
||||
@@ -217,8 +217,10 @@ public final class Controllers {
|
||||
if (targetProperty != null
|
||||
&& Controllers.stage != null
|
||||
&& !Controllers.stage.isIconified()
|
||||
&& !Controllers.stage.isFullScreen()
|
||||
&& !Controllers.stage.isMaximized()) {
|
||||
// https://github.com/HMCL-dev/HMCL/issues/4290
|
||||
&& (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS ||
|
||||
!Controllers.stage.isFullScreen() && !Controllers.stage.isMaximized())
|
||||
) {
|
||||
targetProperty.set(sourceProperty.get());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -22,7 +22,6 @@ import javafx.application.Platform;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
@@ -59,16 +58,10 @@ public class TaskExecutorDialogPane extends BorderPane {
|
||||
lblTitle = new Label();
|
||||
lblTitle.setStyle("-fx-font-size: 14px; -fx-font-weight: BOLD;");
|
||||
|
||||
ScrollPane scrollPane = new ScrollPane();
|
||||
scrollPane.setFitToHeight(true);
|
||||
scrollPane.setFitToWidth(true);
|
||||
VBox.setVgrow(scrollPane, Priority.ALWAYS);
|
||||
{
|
||||
taskListPane = new TaskListPane();
|
||||
scrollPane.setContent(taskListPane);
|
||||
}
|
||||
taskListPane = new TaskListPane();
|
||||
VBox.setVgrow(taskListPane, Priority.ALWAYS);
|
||||
|
||||
center.getChildren().setAll(lblTitle, scrollPane);
|
||||
center.getChildren().setAll(lblTitle, taskListPane);
|
||||
}
|
||||
|
||||
BorderPane bottom = new BorderPane();
|
||||
|
||||
@@ -17,15 +17,20 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import com.jfoenix.controls.JFXProgressBar;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.WeakListener;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.DoubleBinding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ProgressIndicator;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.download.fabric.FabricAPIInstallTask;
|
||||
@@ -52,6 +57,7 @@ import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackCompletionTask;
|
||||
import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackExportTask;
|
||||
import org.jackhuang.hmcl.mod.modrinth.ModrinthCompletionTask;
|
||||
import org.jackhuang.hmcl.mod.modrinth.ModrinthInstallTask;
|
||||
import org.jackhuang.hmcl.mod.modrinth.ModrinthModpackExportTask;
|
||||
import org.jackhuang.hmcl.mod.multimc.MultiMCModpackExportTask;
|
||||
import org.jackhuang.hmcl.mod.multimc.MultiMCModpackInstallTask;
|
||||
import org.jackhuang.hmcl.mod.server.ServerModpackCompletionTask;
|
||||
@@ -63,54 +69,86 @@ import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.task.TaskListener;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.FXThread;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class TaskListPane extends StackPane {
|
||||
private static final Insets DEFAULT_PROGRESS_NODE_PADDING = new Insets(0, 0, 8, 0);
|
||||
private static final Insets STAGED_PROGRESS_NODE_PADDING = new Insets(0, 0, 8, 26);
|
||||
|
||||
private TaskExecutor executor;
|
||||
private final AdvancedListBox listBox = new AdvancedListBox();
|
||||
private final JFXListView<Node> listView = new JFXListView<>();
|
||||
private final Map<Task<?>, ProgressListNode> nodes = new HashMap<>();
|
||||
private final List<StageNode> stageNodes = new ArrayList<>();
|
||||
private final Map<String, StageNode> stageNodes = new HashMap<>();
|
||||
private final ObjectProperty<Insets> progressNodePadding = new SimpleObjectProperty<>(Insets.EMPTY);
|
||||
private final DoubleProperty cellWidth = new SimpleDoubleProperty();
|
||||
|
||||
private Cell lastCell;
|
||||
|
||||
public TaskListPane() {
|
||||
listBox.setSpacing(0);
|
||||
listView.setPadding(new Insets(12, 0, 0, 0));
|
||||
listView.setCellFactory(l -> new Cell());
|
||||
listView.setSelectionModel(null);
|
||||
FXUtils.onChangeAndOperate(listView.widthProperty(), width -> {
|
||||
double w = width.doubleValue();
|
||||
cellWidth.set(w <= 12.0 ? w : w - 12.0);
|
||||
});
|
||||
|
||||
getChildren().setAll(listBox);
|
||||
getChildren().setAll(listView);
|
||||
}
|
||||
|
||||
@FXThread
|
||||
private void addStages(@NotNull Collection<String> stages) {
|
||||
for (String stage : stages) {
|
||||
stageNodes.computeIfAbsent(stage, s -> {
|
||||
StageNode node = new StageNode(stage);
|
||||
listView.getItems().add(node);
|
||||
return node;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@FXThread
|
||||
private void updateProgressNodePadding() {
|
||||
progressNodePadding.set(stageNodes.isEmpty() ? DEFAULT_PROGRESS_NODE_PADDING : STAGED_PROGRESS_NODE_PADDING);
|
||||
}
|
||||
|
||||
public void setExecutor(TaskExecutor executor) {
|
||||
List<String> stages = Lang.removingDuplicates(executor.getStages());
|
||||
this.executor = executor;
|
||||
executor.addTaskListener(new TaskListener() {
|
||||
@Override
|
||||
public void onStart() {
|
||||
Platform.runLater(() -> {
|
||||
stageNodes.clear();
|
||||
listBox.clear();
|
||||
stageNodes.addAll(stages.stream().map(StageNode::new).collect(Collectors.toList()));
|
||||
stageNodes.forEach(listBox::add);
|
||||
|
||||
if (stages.isEmpty()) progressNodePadding.setValue(new Insets(0, 0, 8, 0));
|
||||
else progressNodePadding.setValue(new Insets(0, 0, 8, 26));
|
||||
listView.getItems().clear();
|
||||
addStages(executor.getStages());
|
||||
updateProgressNodePadding();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReady(Task<?> task) {
|
||||
if (task instanceof Task.StagesHintTask) {
|
||||
Platform.runLater(() -> {
|
||||
addStages(((Task<?>.StagesHintTask) task).getStages());
|
||||
updateProgressNodePadding();
|
||||
});
|
||||
}
|
||||
|
||||
if (task.getStage() != null) {
|
||||
Platform.runLater(() -> {
|
||||
stageNodes.stream().filter(x -> x.stage.equals(task.getStage())).findAny().ifPresent(StageNode::begin);
|
||||
StageNode node = stageNodes.get(task.getStage());
|
||||
if (node != null)
|
||||
node.begin();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -123,6 +161,8 @@ public final class TaskListPane extends StackPane {
|
||||
if (task instanceof GameAssetDownloadTask) {
|
||||
task.setName(i18n("assets.download_all"));
|
||||
} else if (task instanceof GameInstallTask) {
|
||||
if (task.getInheritedStage() != null && task.getInheritedStage().startsWith("hmcl.install.game"))
|
||||
return;
|
||||
task.setName(i18n("install.installer.install", i18n("install.installer.game")));
|
||||
} else if (task instanceof ForgeNewInstallTask || task instanceof ForgeOldInstallTask) {
|
||||
task.setName(i18n("install.installer.install", i18n("install.installer.forge")));
|
||||
@@ -156,7 +196,7 @@ public final class TaskListPane extends StackPane {
|
||||
task.setName(i18n("install.installing") + ": " + i18n("modpack.type.server"));
|
||||
} else if (task instanceof HMCLModpackInstallTask) {
|
||||
task.setName(i18n("modpack.installing.given", i18n("modpack.type.hmcl")));
|
||||
} else if (task instanceof McbbsModpackExportTask || task instanceof MultiMCModpackExportTask || task instanceof ServerModpackExportTask) {
|
||||
} else if (task instanceof McbbsModpackExportTask || task instanceof MultiMCModpackExportTask || task instanceof ServerModpackExportTask || task instanceof ModrinthModpackExportTask) {
|
||||
task.setName(i18n("modpack.export"));
|
||||
} else if (task instanceof MinecraftInstanceTask) {
|
||||
task.setName(i18n("modpack.scan"));
|
||||
@@ -169,25 +209,25 @@ public final class TaskListPane extends StackPane {
|
||||
Platform.runLater(() -> {
|
||||
ProgressListNode node = new ProgressListNode(task);
|
||||
nodes.put(task, node);
|
||||
StageNode stageNode = stageNodes.stream().filter(x -> x.stage.equals(task.getInheritedStage())).findAny().orElse(null);
|
||||
listBox.add(listBox.indexOf(stageNode) + 1, node);
|
||||
StageNode stageNode = stageNodes.get(task.getInheritedStage());
|
||||
listView.getItems().add(listView.getItems().indexOf(stageNode) + 1, node);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinished(Task<?> task) {
|
||||
if (task.getStage() != null) {
|
||||
Platform.runLater(() -> {
|
||||
stageNodes.stream().filter(x -> x.stage.equals(task.getStage())).findAny().ifPresent(StageNode::succeed);
|
||||
});
|
||||
}
|
||||
|
||||
Platform.runLater(() -> {
|
||||
if (task.getStage() != null) {
|
||||
StageNode stageNode = stageNodes.get(task.getStage());
|
||||
if (stageNode != null)
|
||||
stageNode.succeed();
|
||||
}
|
||||
|
||||
ProgressListNode node = nodes.remove(task);
|
||||
if (node == null)
|
||||
return;
|
||||
node.unbind();
|
||||
listBox.remove(node);
|
||||
if (node != null) {
|
||||
node.unbind();
|
||||
listView.getItems().remove(node);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -195,25 +235,23 @@ public final class TaskListPane extends StackPane {
|
||||
public void onFailed(Task<?> task, Throwable throwable) {
|
||||
if (task.getStage() != null) {
|
||||
Platform.runLater(() -> {
|
||||
stageNodes.stream().filter(x -> x.stage.equals(task.getStage())).findAny().ifPresent(StageNode::fail);
|
||||
StageNode stageNode = stageNodes.get(task.getStage());
|
||||
if (stageNode != null)
|
||||
stageNode.fail();
|
||||
});
|
||||
}
|
||||
ProgressListNode node = nodes.remove(task);
|
||||
if (node == null)
|
||||
return;
|
||||
Platform.runLater(() -> {
|
||||
node.setThrowable(throwable);
|
||||
});
|
||||
if (node != null)
|
||||
Platform.runLater(() -> node.setThrowable(throwable));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPropertiesUpdate(Task<?> task) {
|
||||
if (task instanceof Task.CountTask) {
|
||||
runInFX(() -> {
|
||||
stageNodes.stream()
|
||||
.filter(x -> x.stage.equals(((Task<?>.CountTask) task).getCountStage()))
|
||||
.findAny()
|
||||
.ifPresent(StageNode::count);
|
||||
StageNode stageNode = stageNodes.get(((Task<?>.CountTask) task).getCountStage());
|
||||
if (stageNode != null)
|
||||
stageNode.count();
|
||||
});
|
||||
|
||||
return;
|
||||
@@ -222,121 +260,251 @@ public final class TaskListPane extends StackPane {
|
||||
if (task.getStage() != null) {
|
||||
int total = tryCast(task.getProperties().get("total"), Integer.class).orElse(0);
|
||||
runInFX(() -> {
|
||||
stageNodes.stream()
|
||||
.filter(x -> x.stage.equals(task.getStage()))
|
||||
.findAny()
|
||||
.ifPresent(stageNode -> {
|
||||
stageNode.setTotal(total);
|
||||
});
|
||||
StageNode stageNode = stageNodes.get(task.getStage());
|
||||
if (stageNode != null)
|
||||
stageNode.addTotal(total);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static class StageNode extends BorderPane {
|
||||
private final String stage;
|
||||
private final class Cell extends ListCell<Node> {
|
||||
private static final double STATUS_ICON_SIZE = 14;
|
||||
|
||||
private final BorderPane pane = new BorderPane();
|
||||
private final StackPane left = new StackPane();
|
||||
private final Label title = new Label();
|
||||
private final Label message = new Label();
|
||||
private final JFXProgressBar bar = new JFXProgressBar();
|
||||
|
||||
private WeakReference<StageNode> prevStageNodeRef;
|
||||
private ChangeListener<StageNode.Status> statusChangeListener;
|
||||
|
||||
private Cell() {
|
||||
setPadding(Insets.EMPTY);
|
||||
|
||||
prefWidthProperty().bind(cellWidth);
|
||||
|
||||
FXUtils.setLimitHeight(left, STATUS_ICON_SIZE);
|
||||
FXUtils.setLimitWidth(left, STATUS_ICON_SIZE);
|
||||
|
||||
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
|
||||
BorderPane.setMargin(left, new Insets(0, 12, 0, 0));
|
||||
BorderPane.setAlignment(title, Pos.CENTER_LEFT);
|
||||
pane.setCenter(title);
|
||||
|
||||
DoubleBinding barWidth = Bindings.createDoubleBinding(() -> {
|
||||
Insets padding = pane.getPadding();
|
||||
Insets insets = pane.getInsets();
|
||||
return pane.getWidth() - padding.getLeft() - padding.getRight() - insets.getLeft() - insets.getRight();
|
||||
}, pane.paddingProperty(), pane.widthProperty());
|
||||
bar.minWidthProperty().bind(barWidth);
|
||||
bar.prefWidthProperty().bind(barWidth);
|
||||
bar.maxWidthProperty().bind(barWidth);
|
||||
|
||||
setGraphic(pane);
|
||||
}
|
||||
|
||||
private void updateLeftIcon(StageNode.Status status) {
|
||||
left.getChildren().setAll(status.svg.createIcon(Theme.blackFill(), STATUS_ICON_SIZE));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(Node item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
// https://mail.openjdk.org/pipermail/openjfx-dev/2022-July/034764.html
|
||||
if (this == lastCell && !isVisible())
|
||||
return;
|
||||
lastCell = this;
|
||||
|
||||
pane.paddingProperty().unbind();
|
||||
title.textProperty().unbind();
|
||||
message.textProperty().unbind();
|
||||
bar.progressProperty().unbind();
|
||||
|
||||
StageNode prevStageNode;
|
||||
if (prevStageNodeRef != null && (prevStageNode = prevStageNodeRef.get()) != null)
|
||||
prevStageNode.status.removeListener(statusChangeListener);
|
||||
|
||||
if (item instanceof ProgressListNode) {
|
||||
var progressListNode = (ProgressListNode) item;
|
||||
title.setText(progressListNode.title);
|
||||
message.textProperty().bind(progressListNode.message);
|
||||
bar.progressProperty().bind(progressListNode.progress);
|
||||
|
||||
pane.paddingProperty().bind(progressNodePadding);
|
||||
pane.setLeft(null);
|
||||
pane.setRight(message);
|
||||
pane.setBottom(bar);
|
||||
} else if (item instanceof StageNode) {
|
||||
var stageNode = (StageNode) item;
|
||||
title.textProperty().bind(stageNode.title);
|
||||
message.setText("");
|
||||
bar.setProgress(ProgressIndicator.INDETERMINATE_PROGRESS);
|
||||
|
||||
pane.setPadding(Insets.EMPTY);
|
||||
pane.setLeft(left);
|
||||
pane.setRight(message);
|
||||
pane.setBottom(null);
|
||||
|
||||
updateLeftIcon(stageNode.status.get());
|
||||
if (statusChangeListener == null)
|
||||
statusChangeListener = new StatusChangeListener(this);
|
||||
stageNode.status.addListener(statusChangeListener);
|
||||
prevStageNodeRef = new WeakReference<>(stageNode);
|
||||
} else { // item == null
|
||||
title.setText("");
|
||||
message.setText("");
|
||||
bar.setProgress(ProgressIndicator.INDETERMINATE_PROGRESS);
|
||||
pane.setPadding(Insets.EMPTY);
|
||||
pane.setLeft(null);
|
||||
pane.setRight(null);
|
||||
pane.setBottom(null);
|
||||
}
|
||||
}
|
||||
|
||||
private final class StatusChangeListener implements ChangeListener<StageNode.Status>, WeakListener {
|
||||
|
||||
private final WeakReference<Cell> cellRef;
|
||||
|
||||
private StatusChangeListener(Cell cell) {
|
||||
this.cellRef = new WeakReference<>(cell);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean wasGarbageCollected() {
|
||||
return cellRef.get() == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends StageNode.Status> observable,
|
||||
StageNode.Status oldValue,
|
||||
StageNode.Status newValue) {
|
||||
Cell cell = cellRef.get();
|
||||
if (cell == null) {
|
||||
if (observable != null)
|
||||
observable.removeListener(this);
|
||||
return;
|
||||
}
|
||||
cell.updateLeftIcon(newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static abstract class Node {
|
||||
|
||||
}
|
||||
|
||||
private static final class StageNode extends Node {
|
||||
private enum Status {
|
||||
WAITING(SVG.MORE_HORIZ),
|
||||
RUNNING(SVG.ARROW_FORWARD),
|
||||
SUCCESS(SVG.CHECK),
|
||||
FAILED(SVG.CLOSE);
|
||||
|
||||
private final SVG svg;
|
||||
|
||||
Status(SVG svg) {
|
||||
this.svg = svg;
|
||||
}
|
||||
}
|
||||
|
||||
private final ObjectProperty<Status> status = new SimpleObjectProperty<>(Status.WAITING);
|
||||
private final StringProperty title = new SimpleStringProperty();
|
||||
private final String message;
|
||||
private int count = 0;
|
||||
private int total = 0;
|
||||
private boolean started = false;
|
||||
|
||||
public StageNode(String stage) {
|
||||
this.stage = stage;
|
||||
private StageNode(String stage) {
|
||||
String stageKey;
|
||||
String stageValue;
|
||||
|
||||
String stageKey = StringUtils.substringBefore(stage, ':');
|
||||
String stageValue = StringUtils.substringAfter(stage, ':');
|
||||
int idx = stage.indexOf(':');
|
||||
if (idx >= 0) {
|
||||
stageKey = stage.substring(0, idx);
|
||||
stageValue = stage.substring(idx + 1);
|
||||
} else {
|
||||
stageKey = stage;
|
||||
stageValue = null;
|
||||
}
|
||||
|
||||
// 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.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.neoforge": message = i18n("install.installer.install", i18n("install.installer.neoforge") + " " + stageValue); break;
|
||||
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.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.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;
|
||||
default: message = i18n(stageKey); 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;
|
||||
}
|
||||
// @formatter:on
|
||||
// CHECKSTYLE:ON
|
||||
|
||||
title.setText(message);
|
||||
BorderPane.setAlignment(title, Pos.CENTER_LEFT);
|
||||
BorderPane.setMargin(title, new Insets(0, 0, 0, 8));
|
||||
setPadding(new Insets(0, 0, 8, 4));
|
||||
setCenter(title);
|
||||
setLeft(FXUtils.limitingSize(SVG.MORE_HORIZ.createIcon(Theme.blackFill(), 14), 14, 14));
|
||||
title.set(message);
|
||||
}
|
||||
|
||||
public void begin() {
|
||||
if (started) return;
|
||||
started = true;
|
||||
setLeft(FXUtils.limitingSize(SVG.ARROW_FORWARD.createIcon(Theme.blackFill(), 14), 14, 14));
|
||||
private void begin() {
|
||||
if (status.get() == Status.WAITING) {
|
||||
status.set(Status.RUNNING);
|
||||
}
|
||||
}
|
||||
|
||||
public void fail() {
|
||||
setLeft(FXUtils.limitingSize(SVG.CLOSE.createIcon(Theme.blackFill(), 14), 14, 14));
|
||||
status.set(Status.FAILED);
|
||||
}
|
||||
|
||||
public void succeed() {
|
||||
setLeft(FXUtils.limitingSize(SVG.CHECK.createIcon(Theme.blackFill(), 14), 14, 14));
|
||||
status.set(Status.SUCCESS);
|
||||
}
|
||||
|
||||
public void count() {
|
||||
updateCounter(++count, total);
|
||||
}
|
||||
|
||||
public void setTotal(int total) {
|
||||
this.total = total;
|
||||
public void addTotal(int n) {
|
||||
this.total += n;
|
||||
updateCounter(count, total);
|
||||
}
|
||||
|
||||
public void updateCounter(int count, int total) {
|
||||
if (total > 0)
|
||||
title.setText(String.format("%s - %d/%d", message, count, total));
|
||||
else
|
||||
title.setText(message);
|
||||
title.setValue(total > 0
|
||||
? message + " - " + count + "/" + total
|
||||
: message
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private class ProgressListNode extends BorderPane {
|
||||
private final JFXProgressBar bar = new JFXProgressBar();
|
||||
private final Label title = new Label();
|
||||
private final Label state = new Label();
|
||||
private final DoubleBinding binding = Bindings.createDoubleBinding(() ->
|
||||
getWidth() - getPadding().getLeft() - getPadding().getRight() - getInsets().getLeft() - getInsets().getRight(),
|
||||
paddingProperty(), widthProperty());
|
||||
private static final class ProgressListNode extends Node {
|
||||
private final String title;
|
||||
private final StringProperty message = new SimpleStringProperty("");
|
||||
private final DoubleProperty progress = new SimpleDoubleProperty(0.0);
|
||||
|
||||
public ProgressListNode(Task<?> task) {
|
||||
bar.progressProperty().bind(task.progressProperty());
|
||||
title.setText(task.getName());
|
||||
state.textProperty().bind(task.messageProperty());
|
||||
|
||||
setLeft(title);
|
||||
setRight(state);
|
||||
setBottom(bar);
|
||||
|
||||
bar.minWidthProperty().bind(binding);
|
||||
bar.prefWidthProperty().bind(binding);
|
||||
bar.maxWidthProperty().bind(binding);
|
||||
|
||||
paddingProperty().bind(progressNodePadding);
|
||||
private ProgressListNode(Task<?> task) {
|
||||
this.title = task.getName();
|
||||
message.bind(task.messageProperty());
|
||||
progress.bind(task.progressProperty());
|
||||
}
|
||||
|
||||
public void unbind() {
|
||||
bar.progressProperty().unbind();
|
||||
state.textProperty().unbind();
|
||||
message.unbind();
|
||||
progress.unbind();
|
||||
}
|
||||
|
||||
public void setThrowable(Throwable throwable) {
|
||||
unbind();
|
||||
state.setText(throwable.getLocalizedMessage());
|
||||
bar.setProgress(0);
|
||||
message.set(throwable.getLocalizedMessage());
|
||||
progress.set(0.);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,7 @@ import org.jackhuang.hmcl.ui.construct.JFXDialogPane;
|
||||
import org.jackhuang.hmcl.ui.wizard.Refreshable;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -164,13 +165,16 @@ public class DecoratorController {
|
||||
// press ESC to go back
|
||||
onEscPressed(navigator, this::back);
|
||||
|
||||
// press F11 to toggle full screen
|
||||
navigator.addEventHandler(KeyEvent.KEY_PRESSED, e -> {
|
||||
if (e.getCode() == KeyCode.F11) {
|
||||
stage.setFullScreen(!stage.isFullScreen());
|
||||
e.consume();
|
||||
}
|
||||
});
|
||||
// https://github.com/HMCL-dev/HMCL/issues/4290
|
||||
if (OperatingSystem.CURRENT_OS != OperatingSystem.MACOS) {
|
||||
// press F11 to toggle full screen
|
||||
navigator.addEventHandler(KeyEvent.KEY_PRESSED, e -> {
|
||||
if (e.getCode() == KeyCode.F11) {
|
||||
stage.setFullScreen(!stage.isFullScreen());
|
||||
e.consume();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
// For JavaFX 12+
|
||||
|
||||
@@ -47,6 +47,7 @@ import org.jackhuang.hmcl.ui.animation.AnimationProducer;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.wizard.Navigation;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
private final StackPane root, parent;
|
||||
@@ -89,22 +90,31 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
EventHandler<MouseEvent> onMouseReleased = this::onMouseReleased;
|
||||
EventHandler<MouseEvent> onMouseDragged = this::onMouseDragged;
|
||||
EventHandler<MouseEvent> onMouseMoved = this::onMouseMoved;
|
||||
onWindowsStatusChange = observable -> {
|
||||
if (primaryStage.isIconified() || primaryStage.isFullScreen() || primaryStage.isMaximized()) {
|
||||
root.removeEventFilter(MouseEvent.MOUSE_RELEASED, onMouseReleased);
|
||||
root.removeEventFilter(MouseEvent.MOUSE_DRAGGED, onMouseDragged);
|
||||
root.removeEventFilter(MouseEvent.MOUSE_MOVED, onMouseMoved);
|
||||
} else {
|
||||
root.addEventFilter(MouseEvent.MOUSE_RELEASED, onMouseReleased);
|
||||
root.addEventFilter(MouseEvent.MOUSE_DRAGGED, onMouseDragged);
|
||||
root.addEventFilter(MouseEvent.MOUSE_MOVED, onMouseMoved);
|
||||
}
|
||||
};
|
||||
WeakInvalidationListener weakOnWindowsStatusChange = new WeakInvalidationListener(onWindowsStatusChange);
|
||||
primaryStage.iconifiedProperty().addListener(weakOnWindowsStatusChange);
|
||||
primaryStage.maximizedProperty().addListener(weakOnWindowsStatusChange);
|
||||
primaryStage.fullScreenProperty().addListener(weakOnWindowsStatusChange);
|
||||
onWindowsStatusChange.invalidated(null);
|
||||
|
||||
// https://github.com/HMCL-dev/HMCL/issues/4290
|
||||
if (OperatingSystem.CURRENT_OS != OperatingSystem.MACOS) {
|
||||
onWindowsStatusChange = observable -> {
|
||||
if (primaryStage.isIconified() || primaryStage.isFullScreen() || primaryStage.isMaximized()) {
|
||||
root.removeEventFilter(MouseEvent.MOUSE_RELEASED, onMouseReleased);
|
||||
root.removeEventFilter(MouseEvent.MOUSE_DRAGGED, onMouseDragged);
|
||||
root.removeEventFilter(MouseEvent.MOUSE_MOVED, onMouseMoved);
|
||||
} else {
|
||||
root.addEventFilter(MouseEvent.MOUSE_RELEASED, onMouseReleased);
|
||||
root.addEventFilter(MouseEvent.MOUSE_DRAGGED, onMouseDragged);
|
||||
root.addEventFilter(MouseEvent.MOUSE_MOVED, onMouseMoved);
|
||||
}
|
||||
};
|
||||
WeakInvalidationListener weakOnWindowsStatusChange = new WeakInvalidationListener(onWindowsStatusChange);
|
||||
primaryStage.iconifiedProperty().addListener(weakOnWindowsStatusChange);
|
||||
primaryStage.maximizedProperty().addListener(weakOnWindowsStatusChange);
|
||||
primaryStage.fullScreenProperty().addListener(weakOnWindowsStatusChange);
|
||||
onWindowsStatusChange.invalidated(null);
|
||||
} else {
|
||||
onWindowsStatusChange = null;
|
||||
root.addEventFilter(MouseEvent.MOUSE_RELEASED, onMouseReleased);
|
||||
root.addEventFilter(MouseEvent.MOUSE_DRAGGED, onMouseDragged);
|
||||
root.addEventFilter(MouseEvent.MOUSE_MOVED, onMouseMoved);
|
||||
}
|
||||
|
||||
shadowContainer.getChildren().setAll(parent);
|
||||
root.getChildren().setAll(shadowContainer);
|
||||
@@ -177,12 +187,16 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
|
||||
BorderPane titleBar = new BorderPane();
|
||||
titleContainer.getChildren().add(titleBar);
|
||||
titleBar.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
|
||||
if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) {
|
||||
primaryStage.setMaximized(!primaryStage.isMaximized());
|
||||
event.consume();
|
||||
}
|
||||
});
|
||||
|
||||
// https://github.com/HMCL-dev/HMCL/issues/4290
|
||||
if (OperatingSystem.CURRENT_OS != OperatingSystem.MACOS) {
|
||||
titleBar.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
|
||||
if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) {
|
||||
primaryStage.setMaximized(!primaryStage.isMaximized());
|
||||
event.consume();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Rectangle buttonsContainerPlaceHolder = new Rectangle();
|
||||
{
|
||||
|
||||
@@ -85,6 +85,7 @@ public final class UpdateInstallerWizardProvider implements WizardProvider {
|
||||
ret = ret.thenComposeAsync(version -> dependencyManager.installLibraryAsync(version, remoteVersion));
|
||||
stages.add(String.format("hmcl.install.%s:%s", remoteVersion.getLibraryId(), remoteVersion.getSelfVersion()));
|
||||
if ("game".equals(remoteVersion.getLibraryId())) {
|
||||
stages.add("hmcl.install.libraries");
|
||||
stages.add("hmcl.install.assets");
|
||||
}
|
||||
} else if (value instanceof RemoveVersionAction) {
|
||||
|
||||
@@ -200,7 +200,7 @@ public final class VersionsPage extends Control implements WizardPage, Refreshab
|
||||
case PENDING:
|
||||
case SNAPSHOT:
|
||||
if (versionType == RemoteVersion.Type.SNAPSHOT
|
||||
&& GameVersionNumber.asGameVersion(remoteVersion.getGameVersion()).isSpecial()) {
|
||||
&& GameVersionNumber.asGameVersion(remoteVersion.getGameVersion()).isAprilFools()) {
|
||||
content.getTags().setAll(i18n("version.game.april_fools"));
|
||||
content.setImage(VersionIconType.APRIL_FOOLS.getIcon());
|
||||
} else {
|
||||
@@ -492,7 +492,7 @@ public final class VersionsPage extends Control implements WizardPage, Refreshab
|
||||
|| versionType == RemoteVersion.Type.PENDING;
|
||||
case APRIL_FOOLS:
|
||||
return versionType == RemoteVersion.Type.SNAPSHOT
|
||||
&& GameVersionNumber.asGameVersion(it.getGameVersion()).isSpecial();
|
||||
&& GameVersionNumber.asGameVersion(it.getGameVersion()).isAprilFools();
|
||||
case OLD:
|
||||
return versionType == RemoteVersion.Type.OLD;
|
||||
case ALL:
|
||||
|
||||
@@ -77,6 +77,10 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
File tempModpack;
|
||||
Task<?> exportTask;
|
||||
|
||||
{
|
||||
setSignificance(TaskSignificance.MODERATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doPreExecute() {
|
||||
return true;
|
||||
@@ -165,6 +169,10 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
return new Task<Void>() {
|
||||
Task<?> dependency = null;
|
||||
|
||||
{
|
||||
setSignificance(TaskSignificance.MODERATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
dependency = new McbbsModpackExportTask(profile.getRepository(), version, exportInfo, modpackFile);
|
||||
@@ -181,6 +189,10 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
return new Task<Void>() {
|
||||
Task<?> dependency;
|
||||
|
||||
{
|
||||
setSignificance(TaskSignificance.MODERATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
VersionSetting vs = profile.getVersionSetting(version);
|
||||
@@ -225,6 +237,10 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
return new Task<Void>() {
|
||||
Task<?> dependency;
|
||||
|
||||
{
|
||||
setSignificance(TaskSignificance.MODERATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
dependency = new ServerModpackExportTask(profile.getRepository(), version, exportInfo, modpackFile);
|
||||
@@ -241,6 +257,10 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
return new Task<Void>() {
|
||||
Task<?> dependency;
|
||||
|
||||
{
|
||||
setSignificance(TaskSignificance.MODERATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
dependency = new ModrinthModpackExportTask(
|
||||
|
||||
@@ -70,7 +70,6 @@ import static org.jackhuang.hmcl.ui.FXUtils.ignoreEvent;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.selectedItemPropertyFor;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public class DownloadListPage extends Control implements DecoratorPage, VersionPage.VersionLoadable {
|
||||
protected final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();
|
||||
@@ -548,7 +547,6 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
if (StringUtils.isNotBlank(dataItem.getIconUrl())) {
|
||||
LOG.debug("Icon: " + dataItem.getIconUrl());
|
||||
imageView.imageProperty().bind(FXUtils.newRemoteImage(dataItem.getIconUrl(), 40, 40, true, true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,12 +26,14 @@ import javafx.beans.value.ChangeListener;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.geometry.Rectangle2D;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.Toggle;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Screen;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.java.JavaManager;
|
||||
import org.jackhuang.hmcl.setting.*;
|
||||
@@ -394,14 +396,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
cboWindowsSize.setEditable(true);
|
||||
cboWindowsSize.setStyle("-fx-padding: 4 4 4 16");
|
||||
cboWindowsSize.setPromptText("854x480");
|
||||
cboWindowsSize.getItems().addAll(
|
||||
"854x480",
|
||||
"1280x720",
|
||||
"1600x900",
|
||||
"1920x1080",
|
||||
"2560x1440",
|
||||
"3840x2160"
|
||||
);
|
||||
cboWindowsSize.getItems().setAll(getSupportedResolutions());
|
||||
|
||||
chkFullscreen = new JFXCheckBox();
|
||||
right.setRight(chkFullscreen);
|
||||
@@ -784,6 +779,28 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
FXUtils.limitSize(iconPickerItem.getImageView(), 32, 32);
|
||||
}
|
||||
|
||||
private static List<String> getSupportedResolutions() {
|
||||
int maxScreenWidth = 0;
|
||||
int maxScreenHeight = 0;
|
||||
|
||||
for (Screen screen : Screen.getScreens()) {
|
||||
Rectangle2D bounds = screen.getBounds();
|
||||
int screenWidth = (int) (bounds.getWidth() * screen.getOutputScaleX());
|
||||
int screenHeight = (int) (bounds.getHeight() * screen.getOutputScaleY());
|
||||
|
||||
maxScreenWidth = Math.max(maxScreenWidth, screenWidth);
|
||||
maxScreenHeight = Math.max(maxScreenHeight, screenHeight);
|
||||
}
|
||||
|
||||
List<String> resolutions = new ArrayList<>(List.of("854x480", "1280x720", "1600x900"));
|
||||
|
||||
if (maxScreenWidth >= 1920 && maxScreenHeight >= 1080) resolutions.add("1920x1080");
|
||||
if (maxScreenWidth >= 2560 && maxScreenHeight >= 1440) resolutions.add("2560x1440");
|
||||
if (maxScreenWidth >= 3840 && maxScreenHeight >= 2160) resolutions.add("3840x2160");
|
||||
|
||||
return resolutions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||
return state.getReadOnlyProperty();
|
||||
|
||||
@@ -30,9 +30,9 @@ public final class ChunkBaseApp {
|
||||
private static final GameVersionNumber MIN_GAME_VERSION = GameVersionNumber.asGameVersion("1.7");
|
||||
|
||||
private static final String[] SEED_MAP_GAME_VERSIONS = {
|
||||
"1.21.5", "1.21.4", "1.21.2", "1.21", "1.20", "1.19.3", "1.19",
|
||||
"1.18", "1.17", "1.16", "1.15", "1.14", "1.13", "1.12", "1.11",
|
||||
"1.10", "1.9", "1.8", "1.7"
|
||||
"1.21.6", "1.21.5", "1.21.4", "1.21.2", "1.21", "1.20", "1.19.3",
|
||||
"1.19", "1.18", "1.17", "1.16", "1.15", "1.14", "1.13", "1.12",
|
||||
"1.11", "1.10", "1.9", "1.8", "1.7"
|
||||
};
|
||||
|
||||
public static final String[] STRONGHOLD_FINDER_GAME_VERSIONS = {
|
||||
|
||||
@@ -46,7 +46,6 @@ import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.ChecksumMismatchException;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
import org.jackhuang.hmcl.util.io.JarUtils;
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
import org.jackhuang.hmcl.util.platform.Platform;
|
||||
|
||||
@@ -63,6 +62,7 @@ import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
@@ -261,9 +261,15 @@ public final class SelfDependencyPatcher {
|
||||
.map(DependencyDescriptor::localPath)
|
||||
.toArray(Path[]::new);
|
||||
|
||||
String[] addOpens = JarUtils.getManifestAttribute("Add-Opens", "").split(" ");
|
||||
String addOpens = null;
|
||||
try (InputStream input = SelfDependencyPatcher.class.getResourceAsStream("/META-INF/MANIFEST.MF")) {
|
||||
if (input != null)
|
||||
addOpens = new Manifest(input).getMainAttributes().getValue("Add-Opens");
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to read MANIFEST.MF file", e);
|
||||
}
|
||||
|
||||
JavaFXPatcher.patch(modules, jars, addOpens);
|
||||
JavaFXPatcher.patch(modules, jars, addOpens != null ? addOpens.split(" ") : new String[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -52,43 +52,43 @@
|
||||
/*
|
||||
Colors are picked from bootstrap
|
||||
|
||||
https://getbootstrap.com/docs/4.1/components/alerts/
|
||||
https://getbootstrap.com/docs/5.3/components/alerts/
|
||||
*/
|
||||
|
||||
.hint.info {
|
||||
-fx-background-color: #cce5ff;
|
||||
-fx-border-color: #b8daff;
|
||||
-fx-background-color: #cfe2ff;
|
||||
-fx-border-color: #9ec5fe;
|
||||
}
|
||||
|
||||
.hint.info Text, .hint.info .svg {
|
||||
-fx-fill: #004085;
|
||||
-fx-fill: #052c65;
|
||||
}
|
||||
|
||||
.hint.success {
|
||||
-fx-background-color: #d4edda;
|
||||
-fx-border-color: #c3e6cb;
|
||||
-fx-background-color: #d1e7dd;
|
||||
-fx-border-color: #a3cfbb;
|
||||
}
|
||||
|
||||
.hint.success Text, .hint.success .svg {
|
||||
-fx-fill: #155724;
|
||||
-fx-fill: #0a3622;
|
||||
}
|
||||
|
||||
.hint.error {
|
||||
-fx-background-color: #f8d7da;
|
||||
-fx-border-color: #f5c6cb;
|
||||
-fx-border-color: #f1aeb5;
|
||||
}
|
||||
|
||||
.hint.error Text, .hint.error .svg {
|
||||
-fx-fill: #721c24;
|
||||
-fx-fill: #58151c;
|
||||
}
|
||||
|
||||
.hint.warning {
|
||||
-fx-background-color: #fff3cd;
|
||||
-fx-border-color: #ffeeba;
|
||||
-fx-border-color: #ffe69c;
|
||||
}
|
||||
|
||||
.hint.warning Text, .hint.warning .svg {
|
||||
-fx-fill: #856404;
|
||||
-fx-fill: #664d03;
|
||||
}
|
||||
|
||||
.skin-pane .jfx-text-field {
|
||||
|
||||
@@ -40,7 +40,7 @@ about.thanks_to.zkitefly.statement=Responsible for maintaining the documentation
|
||||
about.thanks_to.mcbbs=MCBBS (Minecraft Chinese Forum)
|
||||
about.thanks_to.mcbbs.statement=For providing the mcbbs.net download mirror for Chinese Mainland users. (No longer available)
|
||||
about.thanks_to.mcim=mcmod-info-mirror
|
||||
about.thanks_to.mcim.statement=Provides mod information cache acceleration service
|
||||
about.thanks_to.mcim.statement=For providing the mod info cache acceleration service for Chinese Mainland users.
|
||||
about.thanks_to.mcmod=MCMod (mcmod.cn)
|
||||
about.thanks_to.mcmod.statement=For providing the Simplified Chinese translations and wiki for various mods.
|
||||
about.thanks_to.red_lnn.statement=For providing the default background image.
|
||||
@@ -827,6 +827,8 @@ launcher.crash.java_internal_error=Hello Minecraft! Launcher has encountered a f
|
||||
launcher.crash.hmcl_out_dated=Hello Minecraft! Launcher has encountered a fatal error! Your launcher is outdated. Please update your launcher!
|
||||
launcher.update_java=Please update your Java version.
|
||||
|
||||
libraries.download=Downloading Libraries
|
||||
|
||||
login.empty_username=You have not set your username yet!
|
||||
login.enter_password=Please enter your password.
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ about.thanks_to.zekerzhayard.statement=Aportar mucho apoyo técnico a HMCL.
|
||||
about.thanks_to.zkitefly.statement=Responsable del mantenimiento de la documentación de HMCL.
|
||||
about.thanks_to.mcbbs=MCBBS (Foro Chino Minecraft)
|
||||
about.thanks_to.mcbbs.statement=Por proporcionar el espejo de descarga de mcbbs.net para los usuarios de China continental. (Ya no disponible)
|
||||
about.thanks_to.mcim=mcmod-info-mirror
|
||||
about.thanks_to.mcim.statement=Por proporcionar el servicio de aceleración de caché de información de mods para los usuarios de China continental.
|
||||
about.thanks_to.mcmod=MCMod (mcmod.cn)
|
||||
about.thanks_to.mcmod.statement=Por proporcionar las traducciones al chino simplificado y la wiki de varios mods.
|
||||
about.thanks_to.red_lnn.statement=Por proporcionar la imagen de fondo por defecto.
|
||||
@@ -719,7 +721,7 @@ install.installer.external_version=%s (Instalado por un proceso externo, que no
|
||||
install.installing=Instalando
|
||||
install.modpack=Añadir modpack
|
||||
install.modpack.installation=Instalación de modpack
|
||||
install.name.invalid=El nombre contiene caracteres no ASCII (como emoji o caracteres CJK).\n\nSe recomienda cambiar el nombre para que sólo incluya letras, números y guiones bajos en inglés para evitar posibles problemas al iniciar el juego.\n\n¿Desea continuar con la instalación?
|
||||
install.name.invalid=El nombre contiene caracteres especiales (como emoji o caracteres CJK).\n\nSe recomienda cambiar el nombre para que sólo incluya letras, números y guiones bajos en inglés para evitar posibles problemas al iniciar el juego.\n\n¿Desea continuar con la instalación?
|
||||
install.new_game=Añadir instancia
|
||||
install.new_game.already_exists=Esta instancia ya existe. Por favor, utilice otro nombre.
|
||||
install.new_game.current_game_version=Versión actual de la instancia
|
||||
@@ -830,6 +832,8 @@ launcher.crash.java_internal_error=Hello Minecraft! Launcher ha encontrado un er
|
||||
launcher.crash.hmcl_out_dated=Hello Minecraft! Launcher ha encontrado un error fatal. Su launcher está desactualizado. Por favor, ¡actualícelo!
|
||||
launcher.update_java=Por favor, actualice su versión de Java.
|
||||
|
||||
libraries.download=Descargando bibliotecas
|
||||
|
||||
login.empty_username=¡Todavía no has puesto tu nombre de usuario!
|
||||
login.enter_password=Por favor, introduzca su contraseña.
|
||||
|
||||
@@ -1426,12 +1430,18 @@ version.empty=No hay instancias
|
||||
version.empty.add=Añadir una instancia
|
||||
version.empty.launch=No hay instancias disponibles. Si haces clic en «Aceptar», accederás a la página «Descargar». También puedes descargar el juego o cambiar de directorio de juego con los botones «Descargar» o «Todas las instancias» de la página principal de HMCL.
|
||||
version.empty.hint=No hay instancias de Minecraft aquí.\nPuedes intentar cambiar a otro directorio del juego o hacer clic aquí para descargar una.
|
||||
version.game.all=Todos
|
||||
version.game.april_fools=Día de los Inocentes
|
||||
version.game.old=Histórico
|
||||
version.game.release=Lanzamiento
|
||||
version.game.releases=Lanzamientos
|
||||
version.game.snapshot=Snapshot
|
||||
version.game.snapshots=Snapshots
|
||||
version.game.type=Tipo
|
||||
version.launch=Iniciar Minecraft
|
||||
version.launch.empty=Iniciar Minecraft
|
||||
version.launch.empty.installing=Instalando el juego
|
||||
version.launch.empty.tooltip=Instala y ejecuta la última versión oficial del juego.
|
||||
version.launch.test=Probar instalación
|
||||
version.switch=Cambiar Instancia
|
||||
version.launch_script=Exportar script de ejecución
|
||||
@@ -1456,6 +1466,8 @@ version.manage.remove_libraries=Borrar todas las bibliotecas
|
||||
version.manage.rename=Renombrar instancia
|
||||
version.manage.rename.message=Introduzca el nombre de la nueva instancia
|
||||
version.manage.rename.fail=No se ha podido renombrar la instancia, algunos archivos pueden estar en uso o el nombre contiene un carácter no válido.
|
||||
version.search=Nombre
|
||||
version.search.prompt=Introduzca el nombre de la versión para buscar.
|
||||
version.settings=Configuración
|
||||
version.update=Actualizar Modpack
|
||||
|
||||
|
||||
@@ -38,6 +38,8 @@ about.thanks_to.zekerzhayard.statement=Оказал большую технич
|
||||
about.thanks_to.zkitefly.statement=Отвечает за поддержание документации HMCL.
|
||||
about.thanks_to.mcbbs=MCBBS (Китайский форум Minecraft)
|
||||
about.thanks_to.mcbbs.statement=За предоставление зеркала загрузки mcbbs.net для пользователей материкового Китая. (Больше недоступно)
|
||||
about.thanks_to.mcim=mcmod-info-mirror
|
||||
about.thanks_to.mcim.statement=За предоставление услуги ускорения кэширования информации о модах для пользователей из материкового Китая.
|
||||
about.thanks_to.mcmod=MCMod (mcmod.cn)
|
||||
about.thanks_to.mcmod.statement=За предоставление упрощенного китайского перевода и вики для различных модов.
|
||||
about.thanks_to.red_lnn.statement=За предоставление фонового изображения.
|
||||
@@ -715,7 +717,7 @@ install.installer.external_version=%s (Устанавливается внешн
|
||||
install.installing=Установка
|
||||
install.modpack=Установить модпак
|
||||
install.modpack.installation=Установка модпак
|
||||
install.name.invalid=Имя содержит символы, отличные от ASCII (например, эмодзи или символы CJK).\nРекомендуется изменить имя, включив в него только английские буквы, цифры и подчеркивание, чтобы избежать возможных проблем при запуске игры.\nВы хотите продолжить процесс?
|
||||
install.name.invalid=Имя содержит специальные символы (например, эмодзи или символы CJK).\nРекомендуется изменить имя, включив в него только английские буквы, цифры и подчеркивание, чтобы избежать возможных проблем при запуске игры.\nВы хотите продолжить процесс?
|
||||
install.new_game=Установить сборку
|
||||
install.new_game.already_exists=Это имя сборки уже существует. Пожалуйста, используйте другое имя.
|
||||
install.new_game.current_game_version=Текущая версия сборки
|
||||
@@ -829,6 +831,8 @@ launcher.crash.java_internal_error=Лаунчер столкнулся с фат
|
||||
launcher.crash.hmcl_out_dated=Лаунчер столкнулся с фатальной ошибкой! Ваш лаунчер устарел. Обновите его!
|
||||
launcher.update_java=Пожалуйста, обновите версию Java.
|
||||
|
||||
libraries.download=Скачивание библиотек
|
||||
|
||||
login.empty_username=Имя пользователя не установлено!
|
||||
login.enter_password=Введите пароль.
|
||||
|
||||
@@ -1426,12 +1430,18 @@ version.empty=Нет сборок
|
||||
version.empty.add=Установить новую сборок
|
||||
version.empty.launch=Нет доступных сборок. Нажав «ОК», вы перейдете на страницу «Скачать»\n\nВы также можете скачать игру или переключить директорию игры с помощью кнопок «Скачать» или «Все сборок» на главной странице HMCL.
|
||||
version.empty.hint=Нет сборок.\nВы можете попробовать перейти в другую папку с игрой или нажать здесь, чтобы скачать игру.
|
||||
version.game.all=Все
|
||||
version.game.april_fools=День смеха
|
||||
version.game.old=Исторические
|
||||
version.game.release=Релиз
|
||||
version.game.releases=Релизы
|
||||
version.game.snapshot=Снапшот
|
||||
version.game.snapshots=Снапшоты
|
||||
version.game.type=Тип
|
||||
version.launch=Играть
|
||||
version.launch.empty=Играть
|
||||
version.launch.empty.installing=Установка игры
|
||||
version.launch.empty.tooltip=Установить и запустить последнюю официальную версию игры.
|
||||
version.launch.test=Тестовая игра
|
||||
version.switch=Сменить сборку
|
||||
version.launch_script=Создать сценарий запуска
|
||||
@@ -1456,6 +1466,8 @@ version.manage.remove_libraries=Удалить файлы библиотек
|
||||
version.manage.rename=Переименовать
|
||||
version.manage.rename.message=Введите новое название для этой сборки
|
||||
version.manage.rename.fail=Не удалось переименовать сборку. Возможно, некоторые файлы уже используются или в их имени содержится недопустимый символ.
|
||||
version.search=Название
|
||||
version.search.prompt=Введите название версии для поиска
|
||||
version.settings=Настройки
|
||||
version.update=Обновить модпак
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ about.thanks_to.zkitefly.statement=負責維護 HMCL 的文件
|
||||
about.thanks_to.mcbbs=MCBBS (我的世界中文論壇)
|
||||
about.thanks_to.mcbbs.statement=提供 MCBBS 下載源 (現已停止服務)
|
||||
about.thanks_to.mcim=mcmod-info-mirror
|
||||
about.thanks_to.mcim.statement=提供 Mod 信息緩存加速服務
|
||||
about.thanks_to.mcim.statement=為中國大陸使用者提供模組資訊快取加速服務
|
||||
about.thanks_to.mcmod=MC 百科 (mcmod.cn)
|
||||
about.thanks_to.mcmod.statement=提供模組簡體中文名映射表與模組百科
|
||||
about.thanks_to.red_lnn.statement=提供預設背景圖
|
||||
@@ -643,6 +643,8 @@ launcher.crash.java_internal_error=Hello Minecraft! Launcher 由於目前 Java
|
||||
launcher.crash.hmcl_out_dated=Hello Minecraft! Launcher 遇到了無法處理的錯誤。已偵測到你的啟動器不是最新版本,請更新後重試!
|
||||
launcher.update_java=請更新你的 Java
|
||||
|
||||
libraries.download=下載依賴庫
|
||||
|
||||
login.empty_username=你還未設定使用者名稱!
|
||||
login.enter_password=請輸入你的密碼
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ about.thanks_to.zkitefly.statement=负责维护 HMCL 的文档
|
||||
about.thanks_to.mcbbs=MCBBS (我的世界中文论坛)
|
||||
about.thanks_to.mcbbs.statement=提供 MCBBS 下载源 (现已停止服务)
|
||||
about.thanks_to.mcim=mcmod-info-mirror
|
||||
about.thanks_to.mcim.statement=提供 Mod 信息缓存加速服务
|
||||
about.thanks_to.mcim.statement=为中国大陆用户提供模组信息缓存加速服务
|
||||
about.thanks_to.mcmod=MC 百科 (mcmod.cn)
|
||||
about.thanks_to.mcmod.statement=提供模组简体中文名映射表与模组百科
|
||||
about.thanks_to.red_lnn.statement=提供默认背景图
|
||||
@@ -653,6 +653,8 @@ launcher.crash.java_internal_error=Hello Minecraft! Launcher 由于当前 Java
|
||||
launcher.crash.hmcl_out_dated=Hello Minecraft! Launcher 遇到了无法处理的错误。已检测到你的启动器不是最新版本,请更新后再试。
|
||||
launcher.update_java=请更新你的 Java。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。
|
||||
|
||||
libraries.download=下载依赖库
|
||||
|
||||
login.empty_username=你还未设置用户名!
|
||||
login.enter_password=请输入你的密码
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ public class DefaultGameBuilder extends GameBuilder {
|
||||
Task<Version> libraryTask = Task.supplyAsync(() -> new Version(name));
|
||||
libraryTask = libraryTask.thenComposeAsync(libraryTaskHelper(gameVersion, "game", gameVersion));
|
||||
stages.add("hmcl.install.game:" + gameVersion);
|
||||
stages.add("hmcl.install.libraries");
|
||||
stages.add("hmcl.install.assets");
|
||||
|
||||
for (Map.Entry<String, String> entry : toolVersions.entrySet()) {
|
||||
|
||||
@@ -75,6 +75,7 @@ public final class GameLibrariesTask extends Task<Void> {
|
||||
this.integrityCheck = integrityCheck;
|
||||
this.libraries = libraries;
|
||||
|
||||
setStage("hmcl.install.libraries");
|
||||
setSignificance(TaskSignificance.MODERATE);
|
||||
}
|
||||
|
||||
@@ -115,6 +116,7 @@ public final class GameLibrariesTask extends Task<Void> {
|
||||
|
||||
@Override
|
||||
public void execute() throws IOException {
|
||||
int progress = 0;
|
||||
GameRepository gameRepository = dependencyManager.getGameRepository();
|
||||
for (Library library : libraries) {
|
||||
if (!library.appliesToCurrentEnvironment()) {
|
||||
@@ -135,10 +137,17 @@ public final class GameLibrariesTask extends Task<Void> {
|
||||
}
|
||||
}
|
||||
if (shouldDownloadLibrary(gameRepository, version, library, integrityCheck) && (library.hasDownloadURL() || !"optifine".equals(library.getGroupId()))) {
|
||||
dependencies.add(new LibraryDownloadTask(dependencyManager, file, library));
|
||||
dependencies.add(new LibraryDownloadTask(dependencyManager, file, library).withCounter("hmcl.install.libraries"));
|
||||
} else {
|
||||
dependencyManager.getCacheRepository().tryCacheLibrary(library, file.toPath());
|
||||
}
|
||||
|
||||
updateProgress(++progress, libraries.size());
|
||||
}
|
||||
|
||||
if (!dependencies.isEmpty()) {
|
||||
getProperties().put("total", dependencies.size());
|
||||
notifyPropertiesChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;
|
||||
public final class CurseForgeRemoteModRepository implements RemoteModRepository {
|
||||
|
||||
private static final String PREFIX = "https://api.curseforge.com";
|
||||
private static final String apiKey = System.getProperty("hmcl.curseforge.apikey", JarUtils.getManifestAttribute("CurseForge-Api-Key", ""));
|
||||
private static final String apiKey = System.getProperty("hmcl.curseforge.apikey", JarUtils.getAttribute("hmcl.curseforge.apikey", ""));
|
||||
|
||||
private static final int WORD_PERFECT_MATCH_WEIGHT = 5;
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||
import org.jackhuang.hmcl.event.EventManager;
|
||||
import org.jackhuang.hmcl.util.InvocationDispatcher;
|
||||
import org.jackhuang.hmcl.util.ReflectionHelper;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalConsumer;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
|
||||
@@ -35,10 +34,9 @@ import java.util.*;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.function.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
@@ -1009,8 +1007,19 @@ public abstract class Task<T> {
|
||||
void execute(T result, Exception exception) throws Exception;
|
||||
}
|
||||
|
||||
private static final String PACKAGE_PREFIX = Task.class.getPackageName() + ".";
|
||||
private static final Predicate<StackWalker.StackFrame> PREDICATE = stackFrame -> !stackFrame.getClassName().startsWith(PACKAGE_PREFIX);
|
||||
private static final Function<Stream<StackWalker.StackFrame>, Optional<StackWalker.StackFrame>> FUNCTION = stream -> stream.filter(PREDICATE).findFirst();
|
||||
private static final Function<StackWalker.StackFrame, String> FRAME_MAPPING = frame -> {
|
||||
String fileName = frame.getFileName();
|
||||
if (fileName != null)
|
||||
return frame.getClassName() + '.' + frame.getMethodName() + '(' + fileName + ':' + frame.getLineNumber() + ')';
|
||||
else
|
||||
return frame.getClassName() + '.' + frame.getMethodName();
|
||||
};
|
||||
|
||||
private static String getCaller() {
|
||||
return ReflectionHelper.getCaller(packageName -> !"org.jackhuang.hmcl.task".equals(packageName)).toString();
|
||||
return StackWalker.getInstance().walk(FUNCTION).map(FRAME_MAPPING).orElse("Unknown");
|
||||
}
|
||||
|
||||
private static final class SimpleTask<T> extends Task<T> {
|
||||
|
||||
@@ -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.util;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class ReflectionHelper {
|
||||
private ReflectionHelper() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get caller, this method is caller sensitive.
|
||||
*
|
||||
* @param packageFilter returns false if we consider the given package is internal calls, not the caller
|
||||
* @return the caller, method name, source file, line number
|
||||
*/
|
||||
public static StackTraceElement getCaller(Predicate<String> packageFilter) {
|
||||
StackTraceElement[] elements = Thread.currentThread().getStackTrace();
|
||||
// element[0] is Thread.currentThread().getStackTrace()
|
||||
// element[1] is ReflectionHelper.getCaller(packageFilter)
|
||||
// so element[2] is caller of this method.
|
||||
StackTraceElement caller = elements[2];
|
||||
for (int i = 3; i < elements.length; ++i) {
|
||||
if (packageFilter.test(StringUtils.substringBeforeLast(elements[i].getClassName(), '.')) &&
|
||||
!caller.getClassName().equals(elements[i].getClassName()))
|
||||
return elements[i];
|
||||
}
|
||||
return caller;
|
||||
}
|
||||
}
|
||||
@@ -20,47 +20,51 @@ package org.jackhuang.hmcl.util.io;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileSystemNotFoundException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.CodeSource;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.Properties;
|
||||
|
||||
public final class JarUtils {
|
||||
private JarUtils() {
|
||||
}
|
||||
|
||||
private static final Path THIS_JAR;
|
||||
|
||||
private static final Manifest manifest;
|
||||
private static final Properties properties = new Properties();
|
||||
|
||||
static {
|
||||
CodeSource cs = JarUtils.class.getProtectionDomain().getCodeSource();
|
||||
Class<?> entryPointClass = null;
|
||||
CodeSource cs = null;
|
||||
try {
|
||||
entryPointClass = Class.forName("org.jackhuang.hmcl.EntryPoint");
|
||||
cs = entryPointClass.getProtectionDomain().getCodeSource();
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
}
|
||||
|
||||
if (cs == null) {
|
||||
THIS_JAR = null;
|
||||
manifest = new Manifest();
|
||||
} else {
|
||||
Path path;
|
||||
try {
|
||||
path = Paths.get(cs.getLocation().toURI()).toAbsolutePath();
|
||||
path = Path.of(cs.getLocation().toURI()).toAbsolutePath();
|
||||
} catch (FileSystemNotFoundException | IllegalArgumentException | URISyntaxException e) {
|
||||
path = null;
|
||||
}
|
||||
if (path == null || !Files.isRegularFile(path)) {
|
||||
THIS_JAR = null;
|
||||
manifest = new Manifest();
|
||||
} else {
|
||||
THIS_JAR = path;
|
||||
Manifest mn;
|
||||
try (JarFile file = new JarFile(path.toFile())) {
|
||||
mn = file.getManifest();
|
||||
} catch (IOException e) {
|
||||
mn = new Manifest();
|
||||
THIS_JAR = path != null && Files.isRegularFile(path) ? path : null;
|
||||
}
|
||||
|
||||
if (entryPointClass != null) {
|
||||
InputStream input = entryPointClass.getResourceAsStream("/assets/hmcl.properties");
|
||||
if (input != null) {
|
||||
try (var reader = new InputStreamReader(input, StandardCharsets.UTF_8)) {
|
||||
properties.load(reader);
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
manifest = mn;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,8 +74,7 @@ public final class JarUtils {
|
||||
return THIS_JAR;
|
||||
}
|
||||
|
||||
public static String getManifestAttribute(String name, String defaultValue) {
|
||||
String value = manifest.getMainAttributes().getValue(name);
|
||||
return value != null ? value : defaultValue;
|
||||
public static String getAttribute(String name, String defaultValue) {
|
||||
return properties.getProperty(name, defaultValue);
|
||||
}
|
||||
}
|
||||
@@ -186,8 +186,8 @@ public final class NetworkUtils {
|
||||
}
|
||||
}
|
||||
|
||||
// Invalid surrogate pair, encode as '?'
|
||||
builder.append("%3F");
|
||||
// Invalid surrogate pair, encode as U+FFFD (replacement character)
|
||||
encodeCodePoint(builder, 0xfffd);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
+4
@@ -27,6 +27,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -93,6 +94,9 @@ final class WindowsGPUDetector {
|
||||
return null;
|
||||
} else if (object instanceof String[]) {
|
||||
return String.join(" ", (String[]) object);
|
||||
} else if (object instanceof byte[]) {
|
||||
return new String((byte[]) object, StandardCharsets.UTF_16LE)
|
||||
.replace("\0", "");
|
||||
} else {
|
||||
return object.toString();
|
||||
}
|
||||
|
||||
@@ -98,12 +98,20 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public boolean isSpecial() {
|
||||
return this instanceof Special;
|
||||
public boolean isAprilFools() {
|
||||
if (this instanceof Special)
|
||||
return true;
|
||||
|
||||
if (this instanceof Snapshot) {
|
||||
Snapshot snapshot = (Snapshot) this;
|
||||
return snapshot.intValue == Snapshot.toInt(15, 14, 'a');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
enum Type {
|
||||
PRE_CLASSIC, CLASSIC, INFDEV, ALPHA, BETA, NEW
|
||||
PRE_CLASSIC, CLASSIC, INDEV, INFDEV, ALPHA, BETA, NEW
|
||||
}
|
||||
|
||||
abstract Type getType();
|
||||
@@ -141,11 +149,15 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
|
||||
prefixLength = "rd-".length();
|
||||
break;
|
||||
case 'i':
|
||||
if (!value.startsWith("inf-")) {
|
||||
if (value.startsWith("inf-")) {
|
||||
type = Type.INFDEV;
|
||||
prefixLength = "inf-".length();
|
||||
} else if (value.startsWith("in-")) {
|
||||
type = Type.INDEV;
|
||||
prefixLength = "in-".length();
|
||||
} else {
|
||||
throw new IllegalArgumentException(value);
|
||||
}
|
||||
type = Type.INFDEV;
|
||||
prefixLength = "inf-".length();
|
||||
break;
|
||||
case 'a':
|
||||
type = Type.ALPHA;
|
||||
|
||||
@@ -18,17 +18,14 @@
|
||||
package org.jackhuang.hmcl.util.versioning;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.Iterator;
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Copied from org.apache.maven.artifact.versioning.ComparableVersion
|
||||
* Apache License 2.0
|
||||
*
|
||||
* <p>
|
||||
* Maybe we can migrate to org.jenkins-ci:version-number:1.7?
|
||||
*
|
||||
* @see <a href="http://maven.apache.org/pom.html#Version_Order_Specification">Specification</a>
|
||||
*/
|
||||
public final class VersionNumber implements Comparable<VersionNumber> {
|
||||
@@ -220,9 +217,17 @@ public final class VersionNumber implements Comparable<VersionNumber> {
|
||||
*/
|
||||
private static final class StringItem implements Item {
|
||||
private final String value;
|
||||
private final boolean pre;
|
||||
|
||||
StringItem(String value) {
|
||||
this.value = value;
|
||||
|
||||
String lower = value.trim().toLowerCase(Locale.ROOT);
|
||||
this.pre = lower.startsWith("alpha")
|
||||
|| lower.startsWith("beta")
|
||||
|| lower.startsWith("pre")
|
||||
|| lower.startsWith("rc")
|
||||
|| lower.startsWith("experimental");
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
@@ -235,8 +240,8 @@ public final class VersionNumber implements Comparable<VersionNumber> {
|
||||
|
||||
public int compareTo(Item item) {
|
||||
if (item == null) {
|
||||
// 1-string > 1
|
||||
return 1;
|
||||
// 1-beta < 1 < 1-string
|
||||
return pre ? -1 : 1;
|
||||
}
|
||||
switch (item.getType()) {
|
||||
case LONG_ITEM:
|
||||
|
||||
@@ -45,10 +45,10 @@ public class NetworkUtilsTest {
|
||||
assertEquals("https://www.example.com/%E6%B5%8B%E8%AF%95?a=10+20", encodeLocation("https://www.example.com/测试?a=10 20"));
|
||||
|
||||
// Invalid surrogate pair
|
||||
assertEquals("https://www.example.com/%3F", encodeLocation("https://www.example.com/\uD83D"));
|
||||
assertEquals("https://www.example.com/%3F", encodeLocation("https://www.example.com/\uDE07"));
|
||||
assertEquals("https://www.example.com/%3Ftest", encodeLocation("https://www.example.com/\uD83Dtest"));
|
||||
assertEquals("https://www.example.com/%3Ftest", encodeLocation("https://www.example.com/\uDE07test"));
|
||||
assertEquals("https://www.example.com/%EF%BF%BD", encodeLocation("https://www.example.com/\uD83D"));
|
||||
assertEquals("https://www.example.com/%EF%BF%BD", encodeLocation("https://www.example.com/\uDE07"));
|
||||
assertEquals("https://www.example.com/%EF%BF%BDtest", encodeLocation("https://www.example.com/\uD83Dtest"));
|
||||
assertEquals("https://www.example.com/%EF%BF%BDtest", encodeLocation("https://www.example.com/\uDE07test"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -99,9 +99,34 @@ public final class GameVersionNumberTest {
|
||||
assertEquals(VersionNumber.asVersion(versionNumber), old.versionNumber);
|
||||
}
|
||||
|
||||
private static boolean isAprilFools(String version) {
|
||||
return GameVersionNumber.asGameVersion(version).isAprilFools();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsAprilFools() {
|
||||
assertTrue(isAprilFools("15w14a"));
|
||||
assertTrue(isAprilFools("1.RV-Pre1"));
|
||||
assertTrue(isAprilFools("3D Shareware v1.34"));
|
||||
assertTrue(isAprilFools("2.0"));
|
||||
assertTrue(isAprilFools("20w14infinite"));
|
||||
assertTrue(isAprilFools("22w13oneBlockAtATime"));
|
||||
assertTrue(isAprilFools("23w13a_or_b"));
|
||||
assertTrue(isAprilFools("24w14potato"));
|
||||
assertTrue(isAprilFools("25w14craftmine"));
|
||||
|
||||
assertFalse(isAprilFools("1.21.8"));
|
||||
assertFalse(isAprilFools("1.21.8-rc1"));
|
||||
assertFalse(isAprilFools("25w21a"));
|
||||
assertFalse(isAprilFools("13w12~"));
|
||||
assertFalse(isAprilFools("15w14b"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseOld() {
|
||||
assertOldVersion("rd-132211", GameVersionNumber.Type.PRE_CLASSIC, "132211");
|
||||
assertOldVersion("in-20100223", GameVersionNumber.Type.INDEV, "20100223");
|
||||
assertOldVersion("in-20100212-2", GameVersionNumber.Type.INDEV, "20100212-2");
|
||||
assertOldVersion("inf-20100618", GameVersionNumber.Type.INFDEV, "20100618");
|
||||
assertOldVersion("inf-20100330-1", GameVersionNumber.Type.INFDEV, "20100330-1");
|
||||
assertOldVersion("a1.0.6", GameVersionNumber.Type.ALPHA, "1.0.6");
|
||||
|
||||
@@ -90,12 +90,17 @@ public class VersionNumberTest {
|
||||
assertLessThan("1.99999999999999999999", "1.199999999999999999999");
|
||||
assertLessThan("1.99999999999999999999", "2");
|
||||
assertLessThan("1.99999999999999999999", "2.0");
|
||||
assertLessThan("1.0", "1.0-zzz");
|
||||
assertLessThan("1.0-beta.1", "1.0");
|
||||
assertLessThan("1.0-alpha.1", "1.0-beta.1");
|
||||
assertLessThan("3.6.15", "3.6.15.289");
|
||||
assertLessThan("3.6.15.289", "3.6.16");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSorting() {
|
||||
final Comparator<String> comparator = ((Comparator<String>) VersionNumber::compare).thenComparing(String::compareTo);
|
||||
final List<String> input = Collections.unmodifiableList(Arrays.asList(
|
||||
final List<String> input = List.of(
|
||||
"0",
|
||||
"0.10.0",
|
||||
"1.6.4",
|
||||
@@ -120,6 +125,9 @@ public class VersionNumberTest {
|
||||
"1.8.9",
|
||||
"1.8.9-forge1902",
|
||||
"1.9",
|
||||
"1.10-alpha.2",
|
||||
"1.10-beta.1",
|
||||
"1.10-beta.2",
|
||||
"1.10",
|
||||
"1.10.2",
|
||||
"1.10.2-AOE",
|
||||
@@ -140,7 +148,8 @@ public class VersionNumberTest {
|
||||
"1.99999999999999999999",
|
||||
"2",
|
||||
"2.0",
|
||||
"2.1"));
|
||||
"2.1"
|
||||
);
|
||||
|
||||
List<String> output = new ArrayList<>(input);
|
||||
output.sort(comparator);
|
||||
|
||||
+4
-4
@@ -4,13 +4,13 @@
|
||||
|
||||
| | Windows | Linux | macOS | FreeBSD |
|
||||
|----------------------------|:--------------------------------------------------|:---------------------------|:------------------------------------------------------------------------|:---------------------------|
|
||||
| x86-64 | ✅️ | ✅️ | ✅️ | 👌 (Minecraft 1.13~1.21.7) |
|
||||
| x86-64 | ✅️ | ✅️ | ✅️ | 👌 (Minecraft 1.13~1.21.8) |
|
||||
| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / |
|
||||
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.7) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (use Rosetta 2) | ❔ |
|
||||
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.8) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (use Rosetta 2) | ❔ |
|
||||
| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / |
|
||||
| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / |
|
||||
| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.7) | / | / |
|
||||
| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.7) | / | / |
|
||||
| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.8) | / | / |
|
||||
| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.8) | / | / |
|
||||
| LoongArch64 (Old World) | / | 👌 (Minecraft 1.6~1.20.1) | / | / |
|
||||
| PowerPC-64 (Little-Endian) | / | ❔ | / | / |
|
||||
| S390x | / | ❔ | / | / |
|
||||
|
||||
+4
-4
@@ -4,13 +4,13 @@
|
||||
|
||||
| | Windows | Linux | macOS | FreeBSD |
|
||||
|----------------------------|:--------------------------------------------------|:---------------------------|:-----------------------------------------------------------------------|:--------------------------|
|
||||
| x86-64 | ✅️ | ✅️ | ✅️ | 👌(Minecraft 1.13~1.21.7) |
|
||||
| x86-64 | ✅️ | ✅️ | ✅️ | 👌(Minecraft 1.13~1.21.8) |
|
||||
| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / |
|
||||
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.7) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (使用 Rosetta 2) | ❔ |
|
||||
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.8) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (使用 Rosetta 2) | ❔ |
|
||||
| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / |
|
||||
| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / |
|
||||
| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.7) | / | / |
|
||||
| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.7) | / | / |
|
||||
| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.8) | / | / |
|
||||
| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.8) | / | / |
|
||||
| LoongArch64 (旧世界) | / | 👌 (Minecraft 1.6~1.20.1) | / | / |
|
||||
| PowerPC-64 (Little-Endian) | / | ❔ | / | / |
|
||||
| S390x | / | ❔ | / | / |
|
||||
|
||||
+4
-4
@@ -4,13 +4,13 @@
|
||||
|
||||
| | Windows | Linux | macOS | FreeBSD |
|
||||
|----------------------------|:--------------------------------------------------|:---------------------------|:-----------------------------------------------------------------------|:--------------------------|
|
||||
| x86-64 | ✅️ | ✅️ | ✅️ | 👌(Minecraft 1.13~1.21.7) |
|
||||
| x86-64 | ✅️ | ✅️ | ✅️ | 👌(Minecraft 1.13~1.21.8) |
|
||||
| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / |
|
||||
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.7) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (使用 Rosetta 2) | ❔ |
|
||||
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.8) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (使用 Rosetta 2) | ❔ |
|
||||
| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / |
|
||||
| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / |
|
||||
| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.7) | / | / |
|
||||
| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.7) | / | / |
|
||||
| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.8) | / | / |
|
||||
| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.8) | / | / |
|
||||
| LoongArch64 (舊世界) | / | 👌 (Minecraft 1.6~1.20.1) | / | / |
|
||||
| PowerPC-64 (Little-Endian) | / | ❔ | / | / |
|
||||
| S390x | / | ❔ | / | / |
|
||||
|
||||
@@ -52,7 +52,7 @@ Simply execute the following command in the project root directory:
|
||||
./gradlew clean build
|
||||
```
|
||||
|
||||
Make sure you have Java 11 or later installed.
|
||||
Make sure you have Java 17 or later installed.
|
||||
|
||||
## JVM Options (for debugging)
|
||||
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ HMCL 有着强大的跨平台能力。它不仅支持 Windows、Linux、macOS
|
||||
./gradlew clean build
|
||||
```
|
||||
|
||||
请确保你至少安装了 JDK 11 或更高版本。
|
||||
请确保你至少安装了 JDK 17 或更高版本。
|
||||
|
||||
## JVM 选项 (用于调试)
|
||||
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ HMCL 有著強大的跨平臺能力。它不僅支援 Windows、Linux、macOS
|
||||
./gradlew clean build
|
||||
```
|
||||
|
||||
請確保你至少安裝了 JDK 11 或更高版本。
|
||||
請確保你至少安裝了 JDK 17 或更高版本。
|
||||
|
||||
## JVM 選項 (用於除錯)
|
||||
|
||||
|
||||
Reference in new issue
Block a user