Compare commits
33
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ebf58e8fa | ||
|
|
064b39d753 | ||
|
|
c669026312 | ||
|
|
d9b95b28b2 | ||
|
|
d492884927 | ||
|
|
cf4fde5592 | ||
|
|
fd959c41c3 | ||
|
|
e34037c055 | ||
|
|
417c804f48 | ||
|
|
ea6eced136 | ||
|
|
432465ab46 | ||
|
|
03c95cd726 | ||
|
|
730febca8c | ||
|
|
bc41499d1c | ||
|
|
60fae5cc88 | ||
|
|
c11837baa4 | ||
|
|
aaf1218536 | ||
|
|
88637b33bc | ||
|
|
462eea3539 | ||
|
|
d624086594 | ||
|
|
fa8b3a952b | ||
|
|
c70a6c3372 | ||
|
|
0616c37de7 | ||
|
|
b72056f7fe | ||
|
|
3ebe8dce44 | ||
|
|
a9c28573b8 | ||
|
|
9bead7e34e | ||
|
|
d0b36ffa15 | ||
|
|
ea081333e7 | ||
|
|
2b99da430f | ||
|
|
f1ea316bad | ||
|
|
9565c3bdfe | ||
|
|
710fd0f075 |
No files matched your search
@@ -20,16 +20,6 @@ body:
|
||||
* Your launcher is the **latest nightly build**. You can click [here](https://zkitefly.github.io/HMCL-Snapshot-Update/en) to download the latest nightly build.
|
||||
|
||||
If your issue does not fall into the above two categories, you can choose another type of issue or directly go to the [QQ group](https://docs.hmcl.net/groups.html) or [Discord server](https://discord.gg/jVvC7HfM6U) for help.
|
||||
- type: input
|
||||
id: platform
|
||||
attributes:
|
||||
label: 平台 | Platform
|
||||
description: |
|
||||
请输入你遇到问题的平台。
|
||||
Please enter the platform on which you encountered the bug.
|
||||
placeholder: e.g. Windows 11
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: bug-report
|
||||
attributes:
|
||||
@@ -56,4 +46,4 @@ body:
|
||||
If your launcher does not crash, please DO NOT EXIT your launcher, click "Export Launcher Logs" in the "Settings → General → Debug" of the launcher, and drag the exported log to the following input field.
|
||||
**ATTENTION: The crash report or log file is the key to resolving the bug. Please upload them!**
|
||||
validations:
|
||||
required: true
|
||||
required: true
|
||||
@@ -44,7 +44,7 @@ dependencies {
|
||||
implementation(libs.java.info)
|
||||
|
||||
if (launcherExe == null) {
|
||||
implementation("org.glavo.hmcl:HMCLauncher:3.6.0.3")
|
||||
implementation("org.glavo.hmcl:HMCLauncher:3.6.0.4")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,10 +19,13 @@ package org.jackhuang.hmcl;
|
||||
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.JarUtils;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* Stores metadata about this application.
|
||||
@@ -35,6 +38,9 @@ public final class Metadata {
|
||||
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 int MINIMUM_REQUIRED_JAVA_VERSION = 8;
|
||||
public static final int MINIMUM_SUPPORTED_JAVA_VERSION = 11;
|
||||
|
||||
public static final String TITLE = NAME + " " + VERSION;
|
||||
public static final String FULL_TITLE = FULL_NAME + " v" + VERSION;
|
||||
|
||||
@@ -47,7 +53,7 @@ public final class Metadata {
|
||||
public static final String CONTACT_URL = DOCS_URL + "/help.html";
|
||||
public static final String CHANGELOG_URL = DOCS_URL + "/changelog/";
|
||||
public static final String EULA_URL = DOCS_URL + "/eula/hmcl.html";
|
||||
public static final String GROUPS_URL = DOCS_URL + "/groups.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);
|
||||
@@ -93,4 +99,32 @@ public final class Metadata {
|
||||
public static boolean isNightly() {
|
||||
return !isStable() && !isDev();
|
||||
}
|
||||
|
||||
public static @Nullable String getSuggestedJavaDownloadLink() {
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX && Architecture.SYSTEM_ARCH == Architecture.LOONGARCH64_OW)
|
||||
return "https://www.loongnix.cn/zh/api/java/downloads-jdk21/index.html";
|
||||
else {
|
||||
EnumSet<Architecture> supportedArchitectures;
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS)
|
||||
supportedArchitectures = EnumSet.of(Architecture.X86_64, Architecture.X86, Architecture.ARM64);
|
||||
else if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX)
|
||||
supportedArchitectures = EnumSet.of(
|
||||
Architecture.X86_64, Architecture.X86,
|
||||
Architecture.ARM64, Architecture.ARM32,
|
||||
Architecture.RISCV64, Architecture.LOONGARCH64
|
||||
);
|
||||
else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS)
|
||||
supportedArchitectures = EnumSet.of(Architecture.X86_64, Architecture.ARM64);
|
||||
else
|
||||
supportedArchitectures = EnumSet.noneOf(Architecture.class);
|
||||
|
||||
if (supportedArchitectures.contains(Architecture.SYSTEM_ARCH))
|
||||
return String.format("https://docs.hmcl.net/downloads/%s/%s.html",
|
||||
OperatingSystem.CURRENT_OS.getCheckedName(),
|
||||
Architecture.SYSTEM_ARCH.getCheckedName()
|
||||
);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,8 @@ import org.jackhuang.hmcl.util.Lazy;
|
||||
import org.jackhuang.hmcl.util.io.JarUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.SystemUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
@@ -35,6 +37,7 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
@@ -78,7 +81,7 @@ public final class FontManager {
|
||||
return null;
|
||||
});
|
||||
|
||||
private static final ObjectProperty<Font> fontProperty;
|
||||
private static final ObjectProperty<FontReference> fontProperty;
|
||||
|
||||
static {
|
||||
String fontFamily = config().getLauncherFontFamily();
|
||||
@@ -87,10 +90,16 @@ public final class FontManager {
|
||||
if (fontFamily == null)
|
||||
fontFamily = System.getenv("HMCL_FONT");
|
||||
|
||||
Font font = fontFamily == null ? DEFAULT_FONT.get() : Font.font(fontFamily, DEFAULT_FONT_SIZE);
|
||||
fontProperty = new SimpleObjectProperty<>(font);
|
||||
FontReference fontReference;
|
||||
if (fontFamily == null) {
|
||||
Font defaultFont = DEFAULT_FONT.get();
|
||||
fontReference = defaultFont != null ? new FontReference(defaultFont) : null;
|
||||
} else
|
||||
fontReference = new FontReference(fontFamily);
|
||||
|
||||
LOG.info("Font: " + (font != null ? font.getName() : Font.getDefault().getName()));
|
||||
fontProperty = new SimpleObjectProperty<>(fontReference);
|
||||
|
||||
LOG.info("Font: " + (fontReference != null ? fontReference.getFamily() : "System"));
|
||||
fontProperty.addListener((obs, oldValue, newValue) -> {
|
||||
if (newValue != null)
|
||||
config().setLauncherFontFamily(newValue.getFamily());
|
||||
@@ -163,6 +172,16 @@ public final class FontManager {
|
||||
}
|
||||
}
|
||||
|
||||
if (family.indexOf(',') >= 0) {
|
||||
for (String candidateFamily : family.split(",")) {
|
||||
for (Font font : fonts) {
|
||||
if (font.getFamily().equalsIgnoreCase(candidateFamily)) {
|
||||
return font;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LOG.warning(String.format("Family '%s' not found in font file '%s'", family, path));
|
||||
return fonts[0];
|
||||
} catch (NoSuchMethodException | IllegalAccessException ignored) {
|
||||
@@ -179,20 +198,62 @@ public final class FontManager {
|
||||
}
|
||||
}
|
||||
|
||||
public static ObjectProperty<Font> fontProperty() {
|
||||
public static ObjectProperty<FontReference> fontProperty() {
|
||||
return fontProperty;
|
||||
}
|
||||
|
||||
public static Font getFont() {
|
||||
public static FontReference getFont() {
|
||||
return fontProperty.get();
|
||||
}
|
||||
|
||||
public static void setFont(Font font) {
|
||||
public static void setFont(FontReference font) {
|
||||
fontProperty.set(font);
|
||||
}
|
||||
|
||||
public static void setFontFamily(String fontFamily) {
|
||||
setFont(fontFamily != null ? Font.font(fontFamily, DEFAULT_FONT_SIZE) : null);
|
||||
setFont(fontFamily != null ? new FontReference(fontFamily) : null);
|
||||
}
|
||||
|
||||
// https://github.com/HMCL-dev/HMCL/issues/4072
|
||||
public static final class FontReference {
|
||||
private final @NotNull String family;
|
||||
private final @Nullable String style;
|
||||
|
||||
public FontReference(@NotNull String family) {
|
||||
this.family = Objects.requireNonNull(family);
|
||||
this.style = null;
|
||||
}
|
||||
|
||||
public FontReference(@NotNull Font font) {
|
||||
this.family = font.getFamily();
|
||||
this.style = font.getStyle();
|
||||
}
|
||||
|
||||
public @NotNull String getFamily() {
|
||||
return family;
|
||||
}
|
||||
|
||||
public @Nullable String getStyle() {
|
||||
return style;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof FontReference))
|
||||
return false;
|
||||
FontReference that = (FontReference) o;
|
||||
return Objects.equals(family, that.family) && Objects.equals(style, that.style);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(family, style);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("FontReference[family='%s', style='%s']", family, style);
|
||||
}
|
||||
}
|
||||
|
||||
private FontManager() {
|
||||
|
||||
@@ -22,7 +22,6 @@ import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Font;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
@@ -80,9 +79,9 @@ public final class StyleSheets {
|
||||
|
||||
private static String getFontStyleSheet() {
|
||||
final String defaultCss = "/assets/css/font.css";
|
||||
final Font font = FontManager.getFont();
|
||||
final FontManager.FontReference font = FontManager.getFont();
|
||||
|
||||
if (font == null || font == Font.getDefault())
|
||||
if (font == null || "System".equals(font.getFamily()))
|
||||
return defaultCss;
|
||||
|
||||
String fontFamily = font.getFamily();
|
||||
@@ -115,11 +114,7 @@ public final class StyleSheets {
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append(".root {");
|
||||
if (fontFamily == null)
|
||||
// https://github.com/HMCL-dev/HMCL/pull/3423
|
||||
builder.append("-fx-font-family: -fx-base-font-family;");
|
||||
else
|
||||
builder.append("-fx-font-family:\"").append(fontFamily).append("\";");
|
||||
builder.append("-fx-font-family:\"").append(fontFamily).append("\";");
|
||||
|
||||
if (weight != null)
|
||||
builder.append("-fx-font-weight:").append(weight).append(";");
|
||||
|
||||
@@ -63,7 +63,6 @@ import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@@ -312,47 +311,21 @@ public final class Controllers {
|
||||
}
|
||||
|
||||
if (JavaRuntime.CURRENT_VERSION < 10) {
|
||||
Integer shownTipVersion = null;
|
||||
Number shownTipVersion = null;
|
||||
|
||||
try {
|
||||
shownTipVersion = (Integer) config().getShownTips().get(JAVA_VERSION_TIP);
|
||||
shownTipVersion = (Number) config().getShownTips().get(JAVA_VERSION_TIP);
|
||||
} catch (ClassCastException e) {
|
||||
LOG.warning("Invalid type for shown tips key: " + JAVA_VERSION_TIP, e);
|
||||
}
|
||||
|
||||
if (shownTipVersion == null || shownTipVersion < 11) {
|
||||
String downloadLink = null;
|
||||
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX && Architecture.SYSTEM_ARCH == Architecture.LOONGARCH64_OW)
|
||||
downloadLink = "https://www.loongnix.cn/zh/api/java/downloads-jdk21/index.html";
|
||||
else {
|
||||
|
||||
EnumSet<Architecture> supportedArchitectures;
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS)
|
||||
supportedArchitectures = EnumSet.of(Architecture.X86_64, Architecture.X86, Architecture.ARM64);
|
||||
else if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX)
|
||||
supportedArchitectures = EnumSet.of(
|
||||
Architecture.X86_64, Architecture.X86,
|
||||
Architecture.ARM64, Architecture.ARM32,
|
||||
Architecture.RISCV64, Architecture.LOONGARCH64
|
||||
);
|
||||
else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS)
|
||||
supportedArchitectures = EnumSet.of(Architecture.X86_64, Architecture.ARM64);
|
||||
else
|
||||
supportedArchitectures = EnumSet.noneOf(Architecture.class);
|
||||
|
||||
if (supportedArchitectures.contains(Architecture.SYSTEM_ARCH))
|
||||
downloadLink = String.format("https://docs.hmcl.net/downloads/%s/%s.html",
|
||||
OperatingSystem.CURRENT_OS.getCheckedName(),
|
||||
Architecture.SYSTEM_ARCH.getCheckedName()
|
||||
);
|
||||
}
|
||||
|
||||
if (shownTipVersion == null || shownTipVersion.intValue() < Metadata.MINIMUM_SUPPORTED_JAVA_VERSION) {
|
||||
MessageDialogPane.Builder builder = new MessageDialogPane.Builder(i18n("fatal.deprecated_java_version"), null, MessageType.WARNING);
|
||||
String downloadLink = Metadata.getSuggestedJavaDownloadLink();
|
||||
if (downloadLink != null)
|
||||
builder.addHyperLink(i18n("fatal.deprecated_java_version.download_link", 21), downloadLink);
|
||||
Controllers.dialog(builder
|
||||
.ok(() -> config().getShownTips().put(JAVA_VERSION_TIP, 11))
|
||||
.ok(() -> config().getShownTips().put(JAVA_VERSION_TIP, Metadata.MINIMUM_SUPPORTED_JAVA_VERSION))
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ public class GameCrashWindow extends Stage {
|
||||
crashReport = CrashReportAnalyzer.extractCrashReport(rawLog);
|
||||
}
|
||||
|
||||
return pair(CrashReportAnalyzer.anaylze(rawLog), crashReport != null ? CrashReportAnalyzer.findKeywordsFromCrashReport(crashReport) : new HashSet<>());
|
||||
return pair(CrashReportAnalyzer.analyze(rawLog), crashReport != null ? CrashReportAnalyzer.findKeywordsFromCrashReport(crashReport) : new HashSet<>());
|
||||
}), Task.supplyAsync(() -> {
|
||||
Path latestLog = repository.getRunDirectory(version.getId()).toPath().resolve("logs/latest.log");
|
||||
if (!Files.isReadable(latestLog)) {
|
||||
@@ -142,7 +142,7 @@ public class GameCrashWindow extends Stage {
|
||||
return pair(new HashSet<CrashReportAnalyzer.Result>(), new HashSet<String>());
|
||||
}
|
||||
|
||||
return pair(CrashReportAnalyzer.anaylze(log), CrashReportAnalyzer.findKeywordsFromCrashReport(log));
|
||||
return pair(CrashReportAnalyzer.analyze(log), CrashReportAnalyzer.findKeywordsFromCrashReport(log));
|
||||
})).whenComplete(Schedulers.javafx(), (taskResult, exception) -> {
|
||||
loading.set(false);
|
||||
|
||||
|
||||
@@ -58,12 +58,14 @@ public enum SVG {
|
||||
DRESSER("M4 21V5Q4 4.175 4.5875 3.5875T6 3H18Q18.825 3 19.4125 3.5875T20 5V21H18V19H6V21H4ZM6 11H11V5H6V11ZM13 7H18V5H13V7ZM13 11H18V9H13V11ZM10 16H14V14H10V16ZM6 13V17H18V13H6ZM6 13V17 13Z"),
|
||||
EDIT("M5 19H6.425L16.2 9.225 14.775 7.8 5 17.575V19ZM3 21V16.75L16.2 3.575Q16.5 3.3 16.8625 3.15T17.625 3Q18.025 3 18.4 3.15T19.05 3.6L20.425 5Q20.725 5.275 20.8625 5.65T21 6.4Q21 6.8 20.8625 7.1625T20.425 7.825L7.25 21H3ZM19 6.4 17.6 5 19 6.4ZM15.475 8.525 14.775 7.8 16.2 9.225 15.475 8.525Z"),
|
||||
ERROR("M12 17Q12.425 17 12.7125 16.7125T13 16Q13 15.575 12.7125 15.2875T12 15Q11.575 15 11.2875 15.2875T11 16Q11 16.425 11.2875 16.7125T12 17ZM11 13H13V7H11V13ZM12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM12 20Q15.35 20 17.675 17.675T20 12Q20 8.65 17.675 6.325T12 4Q8.65 4 6.325 6.325T4 12Q4 15.35 6.325 17.675T12 20ZM12 12Z"),
|
||||
EXPLORE("M12 12Zm0 8q-3.325 0-5.6625-2.3375T4 12Q4 8.675 6.3375 6.3375T12 4q3.325-0 5.6625 2.3375T20 12q0 3.325-2.3375 5.6625T12 20Zm0 2q2.075-0 3.9-.7875t3.175-2.1375q1.35-1.35 2.1375-3.175T22 12q-0-2.075-.7875-3.9T19.075 4.925q-1.35-1.35-3.175-2.1375T12 2q-2.075 0-3.9.7875T4.925 4.925Q3.575 6.275 2.7875 8.1T2 12q0 2.075.7875 3.9T4.925 19.075q1.35 1.35 3.175 2.1375T12 22Zm0-8.5q.625 0 1.0625-.4375T13.5 12t-.4375-1.0625T12 10.5t-1.0625.4375T10.5 12t.4375 1.0625T12 13.5Zm-4.5 3 2-7 7-2-2 7-7 2Z"),
|
||||
EXTENSION("M8.8 21H5Q4.175 21 3.5875 20.4125T3 19V15.2Q4.2 15.2 5.1 14.4375T6 12.5Q6 11.325 5.1 10.5625T3 9.8V6Q3 5.175 3.5875 4.5875T5 4H9Q9 2.95 9.725 2.225T11.5 1.5Q12.55 1.5 13.275 2.225T14 4H18Q18.825 4 19.4125 4.5875T20 6V10Q21.05 10 21.775 10.725T22.5 12.5Q22.5 13.55 21.775 14.275T20 15V19Q20 19.825 19.4125 20.4125T18 21H14.2Q14.2 19.75 13.4125 18.875T11.5 18Q10.375 18 9.5875 18.875T8.8 21ZM5 19H7.125Q7.725 17.35 9.05 16.675T11.5 16Q12.625 16 13.95 16.675T15.875 19H18V13H20Q20.2 13 20.35 12.85T20.5 12.5Q20.5 12.3 20.35 12.15T20 12H18V6H12V4Q12 3.8 11.85 3.65T11.5 3.5Q11.3 3.5 11.15 3.65T11 4V6H5V8.2Q6.35 8.7 7.175 9.875T8 12.5Q8 13.925 7.175 15.1T5 16.8V19ZM11.5 12.5Z"),
|
||||
FEEDBACK("M12 15Q12.425 15 12.7125 14.7125T13 14Q13 13.575 12.7125 13.2875T12 13Q11.575 13 11.2875 13.2875T11 14Q11 14.425 11.2875 14.7125T12 15ZM11 11H13V5H11V11ZM2 22V4Q2 3.175 2.5875 2.5875T4 2H20Q20.825 2 21.4125 2.5875T22 4V16Q22 16.825 21.4125 17.4125T20 18H6L2 22ZM5.15 16H20V4H4V17.125L5.15 16ZM4 16V4 16Z"),
|
||||
FOLDER("M4 20Q3.175 20 2.5875 19.4125T2 18V6Q2 5.175 2.5875 4.5875T4 4H10L12 6H20Q20.825 6 21.4125 6.5875T22 8V18Q22 18.825 21.4125 19.4125T20 20H4ZM4 18H20V8H11.175L9.175 6H4V18ZM4 18V6 18Z"),
|
||||
FOLDER_COPY("M3 21Q2.175 21 1.5875 20.4125T1 19V6H3V19H20V21H3ZM7 17Q6.175 17 5.5875 16.4125T5 15V4Q5 3.175 5.5875 2.5875T7 2H12L14 4H21Q21.825 4 22.4125 4.5875T23 6V15Q23 15.825 22.4125 16.4125T21 17H7ZM7 15H21V6H13.175L11.175 4H7V15ZM7 15V4 15Z"),
|
||||
FOLDER_OPEN("M4 20Q3.175 20 2.5875 19.4125T2 18V6Q2 5.175 2.5875 4.5875T4 4H10L12 6H20Q20.825 6 21.4125 6.5875T22 8H11.175L9.175 6H4V18L6.4 10H23.5L20.925 18.575Q20.725 19.225 20.1875 19.6125T19 20H4ZM6.1 18H19L20.8 12H7.9L6.1 18ZM6.1 18 7.9 12 6.1 18ZM4 8V6 8Z"),
|
||||
FORMAT_LIST_BULLETED("M9 19V17H21V19H9ZM9 13V11H21V13H9ZM9 7V5H21V7H9ZM5 20Q4.175 20 3.5875 19.4125T3 18Q3 17.175 3.5875 16.5875T5 16Q5.825 16 6.4125 16.5875T7 18Q7 18.825 6.4125 19.4125T5 20ZM5 14Q4.175 14 3.5875 13.4125T3 12Q3 11.175 3.5875 10.5875T5 10Q5.825 10 6.4125 10.5875T7 12Q7 12.825 6.4125 13.4125T5 14ZM5 8Q4.175 8 3.5875 7.4125T3 6Q3 5.175 3.5875 4.5875T5 4Q5.825 4 6.4125 4.5875T7 6Q7 6.825 6.4125 7.4125T5 8Z"),
|
||||
FORT("M1 21V17l2-2V9L1 7V3H3V5H5V3H7V5H9V3h2V7L9 9v1h6V9L13 7V3h2V5h2V3h2V5h2V3h2V7L21 9v6l2 2v4H14V18q0-.825-.5875-1.4125T12 16q-.825 0-1.4125.5875T10 18v3H1Zm2-2H8V18q0-1.65 1.175-2.825T12 14q1.65 0 2.825 1.175T16 18v1h5V17.825l-2-2V8.175L20.175 7h-4.35L17 8.175V12H7V8.175L8.175 7H3.825L5 8.175v7.65l-2 2V19Zm9-6Z"),
|
||||
FOR_YOU("M12 12Q14.025 12 16.225 11.5875T20 10.5V20.5Q18.5 21.175 16.35 21.5875T12 22Q9.8 22 7.65 21.5875T4 20.5V10.5Q5.575 11.175 7.775 11.5875T12 12ZM18 19V13.25Q16.75 13.6 15.1125 13.8T12 14Q10.525 14 8.8875 13.8T6 13.25V19Q7.25 19.45 8.875 19.725T12 20Q13.5 20 15.125 19.725T18 19ZM12 2Q13.65 2 14.825 3.175T16 6Q16 7.65 14.825 8.825T12 10Q10.35 10 9.175 8.825T8 6Q8 4.35 9.175 3.175T12 2ZM12 8Q12.825 8 13.4125 7.4125T14 6Q14 5.175 13.4125 4.5875T12 4Q11.175 4 10.5875 4.5875T10 6Q10 6.825 10.5875 7.4125T12 8ZM12 6ZM12 16.625Z"),
|
||||
GAMEPAD("M12 7.65ZM16.35 12ZM7.65 12ZM12 16.35ZM12 10.5 9 7.5V2H15V7.5L12 10.5ZM16.5 15 13.5 12 16.5 9H22V15H16.5ZM2 15V9H7.5L10.5 12 7.5 15H2ZM9 22V16.5L12 13.5 15 16.5V22H9ZM12 7.65 13 6.65V4H11V6.65L12 7.65ZM4 13H6.65L7.65 12 6.65 11H4V13ZM11 20H13V17.35L12 16.35 11 17.35V20ZM17.35 13H20V11H17.35L16.35 12 17.35 13Z"),
|
||||
GLOBE_BOOK("M3.075 13Q3.05 12.75 3.0375 12.5T3.025 12Q3.025 10.125 3.725 8.4875T5.65 5.6375Q6.875 4.425 8.5 3.7125T12 3Q13.875 3 15.5125 3.7125T18.3625 5.6375Q19.575 6.85 20.2875 8.4875T21 12Q21 12.25 20.9875 12.5T20.95 13H18.925Q18.975 12.75 18.9875 12.5T19 12Q19 11.75 18.9875 11.5T18.925 11H15.975Q16 11.25 16 11.5V12.5Q16 12.75 15.975 13H14V12.175Q14 11.875 13.9875 11.575T13.95 11H10.075Q10.05 11.275 10.0375 11.575T10.025 12.175V13H8.05Q8.025 12.75 8.025 12.5V11.5Q8.025 11.25 8.05 11H5.1Q5.05 11.25 5.0375 11.5T5.025 12Q5.025 12.25 5.0375 12.5T5.1 13H3.075ZM5.7 9H8.275Q8.475 7.925 8.775 7.0625T9.425 5.5Q8.225 5.95 7.25 6.8625T5.7 9ZM10.35 9H13.65Q13.4 7.925 13.025 6.9T12 5Q11.35 5.875 10.9625 6.9T10.35 9ZM15.75 9H18.325Q17.75 7.775 16.7625 6.8625T14.575 5.5Q14.925 6.25 15.2375 7.0875T15.75 9ZM11 21V20Q11 18.75 10.125 17.875T8 17H2V15H8Q9.2 15 10.2375 15.525T12 17Q12.725 16.05 13.7625 15.525T16 15H22V17H16Q14.75 17 13.875 17.875T13 20V21H11Z"),
|
||||
@@ -73,9 +75,11 @@ public enum SVG {
|
||||
INFO("M11 17H13V11H11V17ZM12 9Q12.425 9 12.7125 8.7125T13 8Q13 7.575 12.7125 7.2875T12 7Q11.575 7 11.2875 7.2875T11 8Q11 8.425 11.2875 8.7125T12 9ZM12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM12 20Q15.35 20 17.675 17.675T20 12Q20 8.65 17.675 6.325T12 4Q8.65 4 6.325 6.325T4 12Q4 15.35 6.325 17.675T12 20ZM12 12Z"),
|
||||
KEYBOARD_ARROW_DOWN("M12 15.4 6 9.4 7.4 8 12 12.6 16.6 8 18 9.4 12 15.4Z"),
|
||||
KEYBOARD_ARROW_UP("M12 10.8 7.4 15.4 6 14 12 8 18 14 16.6 15.4 12 10.8Z"),
|
||||
LANDSCAPE("M1 18l6-8 4.5 6H19L14 9.35l-2.5 3.3L10.25 11 14 6l9 12H1Zm13.025-2ZM5 16H9L7 13.325 5 16ZH9 5Z"),
|
||||
LIST("M7 9V7H21V9H7ZM7 13V11H21V13H7ZM7 17V15H21V17H7ZM4 9Q3.575 9 3.2875 8.7125T3 8Q3 7.575 3.2875 7.2875T4 7Q4.425 7 4.7125 7.2875T5 8Q5 8.425 4.7125 8.7125T4 9ZM4 13Q3.575 13 3.2875 12.7125T3 12Q3 11.575 3.2875 11.2875T4 11Q4.425 11 4.7125 11.2875T5 12Q5 12.425 4.7125 12.7125T4 13ZM4 17Q3.575 17 3.2875 16.7125T3 16Q3 15.575 3.2875 15.2875T4 15Q4.425 15 4.7125 15.2875T5 16Q5 16.425 4.7125 16.7125T4 17Z"),
|
||||
LISTS("M2 20V16H6V20H2ZM8 20V16H22V20H8ZM2 14V10H6V14H2ZM8 14V10H22V14H8ZM2 8V4H6V8H2ZM8 8V4H22V8H8Z"),
|
||||
LOCAL_CAFE("M4 21V19H20V21H4ZM8 17Q6.35 17 5.175 15.825T4 13V3H20Q20.825 3 21.4125 3.5875T22 5V8Q22 8.825 21.4125 9.4125T20 10H18V13Q18 14.65 16.825 15.825T14 17H8ZM8 15H14Q14.825 15 15.4125 14.4125T16 13V5H6V13Q6 13.825 6.5875 14.4125T8 15ZM18 8H20V5H18V8ZM8 15H6 16 8Z"),
|
||||
LOCATION_CITY("M3 21V7H9V5l3-3 3 3v6h6V21H3Zm2-2H7V17H5v2Zm0-4H7V13H5v2Zm0-4H7V9H5v2Zm6 8h2V17H11v2Zm0-4h2V13H11v2Zm0-4h2V9H11v2Zm0-4h2V5H11V7Zm6 12h2V17H17v2Zm0-4h2V13H17v2Z"),
|
||||
MENU("M3 18V16H21V18H3ZM3 13V11H21V13H3ZM3 8V6H21V8H3Z"),
|
||||
MICROSOFT("M4 20H22v2H4V13H20v7h2V4H20v7H4V4h7V20h2V4h9V2H2V22H4"), // Not Material
|
||||
MINIMIZE("M6 21V19H18V21H6Z"),
|
||||
@@ -104,6 +108,8 @@ public enum SVG {
|
||||
TRIP("M4 21Q3.175 21 2.5875 20.4125T2 19V8Q2 7.175 2.5875 6.5875T4 6H8V4Q8 3.175 8.5875 2.5875T10 2H14Q14.825 2 15.4125 2.5875T16 4V6H20Q20.825 6 21.4125 6.5875T22 8V19Q22 19.825 21.4125 20.4125T20 21H4ZM10 6H14V4H10V6ZM6 8H4V19H6V8ZM16 19V8H8V19H16ZM18 8V19H20V8H18ZM12 13.5Z"),
|
||||
TUNE("M11 21V15H13V17H21V19H13V21H11ZM3 19V17H9V19H3ZM7 15V13H3V11H7V9H9V15H7ZM11 13V11H21V13H11ZM15 9V3H17V5H21V7H17V9H15ZM3 7V5H13V7H3Z"),
|
||||
UPDATE("M12 21Q10.125 21 8.4875 20.2875T5.6375 18.3625Q4.425 17.15 3.7125 15.5125T3 12Q3 10.125 3.7125 8.4875T5.6375 5.6375Q6.85 4.425 8.4875 3.7125T12 3Q14.05 3 15.8875 3.875T19 6.35V4H21V10H15V8H17.75Q16.725 6.6 15.225 5.8T12 5Q9.075 5 7.0375 7.0375T5 12Q5 14.925 7.0375 16.9625T12 19Q14.625 19 16.5875 17.3T18.9 13H20.95Q20.575 16.425 18.0125 18.7125T12 21ZM14.8 16.2 11 12.4V7H13V11.6L16.2 14.8 14.8 16.2Z"),
|
||||
VISIBILITY("M12 16q1.875 0 3.1875-1.3125T16.5 11.5 15.1875 8.3125 12 7 8.8125 8.3125 7.5 11.5t1.3125 3.1875T12 16Zm0-1.8q-1.125 0-1.9125-.7875T9.3 11.5t.7875-1.9125T12 8.8q1.125 0 1.9125.7875T14.7 11.5q0 1.125-.7875 1.9125T12 14.2ZM12 19q-3.65 0-6.65-2.0375T1 11.5Q2.35 8.075 5.35 6.0375T12 4q3.65 0 6.65 2.0375T23 11.5q-1.35 3.425-4.35 5.4625T12 19Zm0-7.5ZM12 17q2.825 0 5.1875-1.4875T20.8 11.5q-1.25-2.525-3.6125-4.0125T12 6 6.8125 7.4875 3.2 11.5q1.25 2.525 3.6125 4.0125T12 17Z"),
|
||||
VISIBILITY_OFF("M16.1 13.3l-1.45-1.45q.225-1.175-.675-2.2t-2.325-.8L10.2 7.4q.425-.2.8625-.3T12 7q1.875 0 3.1875 1.3125T16.5 11.5q0 .5-.1.9375t-.3.8625Zm3.2 3.15-1.45-1.4q.95-.725 1.6875-1.5875T20.8 11.5q-1.25-2.525-3.5875-4.0125T12 6q-.725 0-1.425.1T9.2 6.4L7.65 4.85q1.025-.425 2.1-.6375T12 4q3.775 0 6.725 2.0875T23 11.5q-.575 1.475-1.5125 2.7375T19.3 16.45Zm.5 6.15-4.2-4.15q-.875.275-1.7625.4125T12 19q-3.775 0-6.725-2.0875T1 11.5q.525-1.325 1.325-2.4625T4.15 7L1.4 4.2 2.8 2.8 21.2 21.2l-1.4 1.4ZM5.55 8.4q-.725.65-1.325 1.425T3.2 11.5q1.25 2.525 3.5875 4.0125T12 17q.5 0 .975-.0625T13.95 16.8l-.9-.95q-.275.075-.525.1125T12 16q-1.875 0-3.1875-1.3125T7.5 11.5q0-.275.0375-.525T7.65 10.45L5.55 8.4Zm7.975 2.325ZM9.75 12.6Z"),
|
||||
WARNING("M1 21 12 2 23 21H1ZM4.45 19H19.55L12 6 4.45 19ZM12 18Q12.425 18 12.7125 17.7125T13 17Q13 16.575 12.7125 16.2875T12 16Q11.575 16 11.2875 16.2875T11 17Q11 17.425 11.2875 17.7125T12 18ZM11 15H13V10H11V15ZM12 12.5Z"),
|
||||
WB_SUNNY("M11 4V1H13V4H11ZM11 23V20H13V23H11ZM20 13V11H23V13H20ZM1 13V11H4V13H1ZM18.7 6.7 17.3 5.3 19.05 3.5 20.5 4.95 18.7 6.7ZM4.95 20.5 3.5 19.05 5.3 17.3 6.7 18.7 4.95 20.5ZM19.05 20.5 17.3 18.7 18.7 17.3 20.5 19.05 19.05 20.5ZM5.3 6.7 3.5 4.95 4.95 3.5 6.7 5.3 5.3 6.7ZM12 18Q9.5 18 7.75 16.25T6 12Q6 9.5 7.75 7.75T12 6Q14.5 6 16.25 7.75T18 12Q18 14.5 16.25 16.25T12 18ZM12 16Q13.675 16 14.8375 14.8375T16 12Q16 10.325 14.8375 9.1625T12 8Q10.325 8 9.1625 9.1625T8 12Q8 13.675 9.1625 14.8375T12 16ZM12 12Z"),
|
||||
;
|
||||
@@ -127,11 +133,10 @@ public enum SVG {
|
||||
return pane;
|
||||
}
|
||||
|
||||
Group svg = new Group(path);
|
||||
double scale = size / 24;
|
||||
svg.setScaleX(scale);
|
||||
svg.setScaleY(scale);
|
||||
return svg;
|
||||
path.setScaleX(scale);
|
||||
path.setScaleY(scale);
|
||||
return new Group(path);
|
||||
}
|
||||
|
||||
public Node createIcon(ObservableValue<? extends Paint> fill, double size) {
|
||||
|
||||
@@ -51,7 +51,7 @@ public final class UpgradeDialog extends JFXDialogLayout {
|
||||
Task.supplyAsync(Schedulers.io(), () -> {
|
||||
Document document = Jsoup.parse(new URL(url), 30 * 1000);
|
||||
Node node = document.selectFirst("#nowchange");
|
||||
if (node == null)
|
||||
if (node == null || !"h1".equals(node.nodeName()))
|
||||
throw new IOException("Cannot find #nowchange in document");
|
||||
|
||||
HTMLRenderer renderer = new HTMLRenderer(uri -> {
|
||||
@@ -60,6 +60,9 @@ public final class UpgradeDialog extends JFXDialogLayout {
|
||||
});
|
||||
|
||||
do {
|
||||
if ("h1".equals(node.nodeName()) && !"nowchange".equals(node.attr("id"))) {
|
||||
break;
|
||||
}
|
||||
renderer.appendNode(node);
|
||||
node = node.nextSibling();
|
||||
} while (node != null);
|
||||
|
||||
@@ -311,8 +311,9 @@ public class DecoratorController {
|
||||
if (navigator.getCurrentPage() instanceof DecoratorPage) {
|
||||
DecoratorPage page = (DecoratorPage) navigator.getCurrentPage();
|
||||
|
||||
// FIXME: Get WorldPage working first, and revisit this later
|
||||
page.closePage();
|
||||
if (page.isPageCloseable()) {
|
||||
page.closePage();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,19 +121,14 @@ public abstract class AbstractInstallersPage extends Control implements WizardPa
|
||||
InstallerItem[] libraries = control.group.getLibraries();
|
||||
|
||||
FlowPane libraryPane = new FlowPane(libraries);
|
||||
libraryPane.setVgap(16);
|
||||
libraryPane.setVgap(8);
|
||||
libraryPane.setHgap(16);
|
||||
|
||||
if (libraries.length <= 8) {
|
||||
BorderPane.setMargin(libraryPane, new Insets(16, 0, 16, 0));
|
||||
root.setCenter(libraryPane);
|
||||
} else {
|
||||
ScrollPane scrollPane = new ScrollPane(libraryPane);
|
||||
scrollPane.setFitToWidth(true);
|
||||
scrollPane.setFitToHeight(true);
|
||||
BorderPane.setMargin(scrollPane, new Insets(16, 0, 16, 0));
|
||||
root.setCenter(scrollPane);
|
||||
}
|
||||
ScrollPane scrollPane = new ScrollPane(libraryPane);
|
||||
scrollPane.setFitToWidth(true);
|
||||
scrollPane.setFitToHeight(true);
|
||||
BorderPane.setMargin(scrollPane, new Insets(16, 0, 16, 0));
|
||||
root.setCenter(scrollPane);
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.download;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
@@ -58,6 +59,8 @@ class AdditionalInstallersPage extends AbstractInstallersPage {
|
||||
reload();
|
||||
});
|
||||
}
|
||||
|
||||
installable.bind(Bindings.createBooleanBinding(() -> compatible.get() && txtName.validate(), txtName.textProperty(), compatible));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.jackhuang.hmcl.download.*;
|
||||
import org.jackhuang.hmcl.download.game.GameRemoteVersion;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository;
|
||||
import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;
|
||||
import org.jackhuang.hmcl.setting.DownloadProviders;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
@@ -72,6 +73,7 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
private final TabHeader.Tab<DownloadListPage> modTab = new TabHeader.Tab<>("modTab");
|
||||
private final TabHeader.Tab<DownloadListPage> modpackTab = new TabHeader.Tab<>("modpackTab");
|
||||
private final TabHeader.Tab<DownloadListPage> resourcePackTab = new TabHeader.Tab<>("resourcePackTab");
|
||||
private final TabHeader.Tab<DownloadListPage> shaderTab = new TabHeader.Tab<>("shaderTab");
|
||||
private final TabHeader.Tab<DownloadListPage> worldTab = new TabHeader.Tab<>("worldTab");
|
||||
private final TransitionPane transitionPane = new TransitionPane();
|
||||
private final DownloadNavigator versionPageNavigator = new DownloadNavigator();
|
||||
@@ -98,8 +100,9 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
}));
|
||||
modTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofMod((profile, version, file) -> download(profile, version, file, "mods"), true)));
|
||||
resourcePackTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofResourcePack((profile, version, file) -> download(profile, version, file, "resourcepacks"), true)));
|
||||
shaderTab.setNodeSupplier(loadVersionFor(() -> new DownloadListPage(ModrinthRemoteModRepository.SHADER_PACKS, (profile, version, file) -> download(profile, version, file, "shaderpacks"), true)));
|
||||
worldTab.setNodeSupplier(loadVersionFor(() -> new DownloadListPage(CurseForgeRemoteModRepository.WORLDS)));
|
||||
tab = new TabHeader(newGameTab, modpackTab, modTab, resourcePackTab, worldTab);
|
||||
tab = new TabHeader(newGameTab, modpackTab, modTab, resourcePackTab, shaderTab, worldTab);
|
||||
|
||||
Profiles.registerVersionsListener(this::loadVersions);
|
||||
|
||||
@@ -115,6 +118,7 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
.startCategory(i18n("download.content").toUpperCase(Locale.ROOT))
|
||||
.addNavigationDrawerTab(tab, modTab, i18n("mods"), SVG.EXTENSION)
|
||||
.addNavigationDrawerTab(tab, resourcePackTab, i18n("resourcepack"), SVG.TEXTURE)
|
||||
.addNavigationDrawerTab(tab, shaderTab, i18n("download.shader"), SVG.WB_SUNNY)
|
||||
.addNavigationDrawerTab(tab, worldTab, i18n("world"), SVG.PUBLIC);
|
||||
FXUtils.setLimitWidth(sideBar, 200);
|
||||
setLeft(sideBar);
|
||||
@@ -186,6 +190,9 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
if (resourcePackTab.isInitialized()) {
|
||||
resourcePackTab.getNode().loadVersion(profile, null);
|
||||
}
|
||||
if (shaderTab.isInitialized()) {
|
||||
shaderTab.getNode().loadVersion(profile, null);
|
||||
}
|
||||
if (worldTab.isInitialized()) {
|
||||
worldTab.getNode().loadVersion(profile, null);
|
||||
}
|
||||
|
||||
@@ -61,6 +61,8 @@ public final class ProfilePage extends BorderPane implements DecoratorPage {
|
||||
* @param profile null if creating a new profile.
|
||||
*/
|
||||
public ProfilePage(Profile profile) {
|
||||
getStyleClass().add("gray-background");
|
||||
|
||||
this.profile = profile;
|
||||
String profileDisplayName = Optional.ofNullable(profile).map(Profiles::getProfileDisplayName).orElse("");
|
||||
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.stage.FileChooser;
|
||||
@@ -28,7 +26,6 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.ListPageBase;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||
|
||||
@@ -41,24 +38,17 @@ import java.util.Objects;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class DatapackListPage extends ListPageBase<DatapackListPageSkin.DatapackInfoObject> implements DecoratorPage {
|
||||
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();
|
||||
public final class DatapackListPage extends ListPageBase<DatapackListPageSkin.DatapackInfoObject> {
|
||||
private final Path worldDir;
|
||||
private final Datapack datapack;
|
||||
|
||||
// Strongly referencing items, preventing GC collection
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private final ObservableList<DatapackListPageSkin.DatapackInfoObject> items;
|
||||
|
||||
public DatapackListPage(String worldName, Path worldDir) {
|
||||
this.worldDir = worldDir;
|
||||
|
||||
state.set(State.fromTitle(i18n("datapack.title", worldName)));
|
||||
public DatapackListPage(WorldManagePage worldManagePage) {
|
||||
this.worldDir = worldManagePage.getWorld().getFile();
|
||||
|
||||
datapack = new Datapack(worldDir.resolve("datapacks"));
|
||||
datapack.loadFromDir();
|
||||
|
||||
setItems(items = MappedObservableList.create(datapack.getInfo(), DatapackListPageSkin.DatapackInfoObject::new));
|
||||
setItems(MappedObservableList.create(datapack.getInfo(), DatapackListPageSkin.DatapackInfoObject::new));
|
||||
|
||||
FXUtils.applyDragListener(this, it -> Objects.equals("zip", FileUtils.getExtension(it)),
|
||||
mods -> mods.forEach(this::installSingleDatapack), this::refresh);
|
||||
@@ -91,11 +81,6 @@ public class DatapackListPage extends ListPageBase<DatapackListPageSkin.Datapack
|
||||
.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||
return state.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public void add() {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle(i18n("datapack.choose_datapack"));
|
||||
|
||||
@@ -20,7 +20,6 @@ package org.jackhuang.hmcl.ui.versions;
|
||||
import com.jfoenix.controls.JFXCheckBox;
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.geometry.Insets;
|
||||
@@ -29,87 +28,57 @@ import javafx.scene.control.SelectionMode;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.mod.Datapack;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.FloatListCell;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.util.Holder;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.ignoreEvent;
|
||||
import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton;
|
||||
import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
class DatapackListPageSkin extends SkinBase<DatapackListPage> {
|
||||
final class DatapackListPageSkin extends SkinBase<DatapackListPage> {
|
||||
|
||||
DatapackListPageSkin(DatapackListPage skinnable) {
|
||||
super(skinnable);
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
root.getStyleClass().add("content-background");
|
||||
StackPane pane = new StackPane();
|
||||
pane.setPadding(new Insets(10));
|
||||
pane.getStyleClass().addAll("notice-pane");
|
||||
|
||||
ComponentList root = new ComponentList();
|
||||
root.getStyleClass().add("no-padding");
|
||||
JFXListView<DatapackInfoObject> listView = new JFXListView<>();
|
||||
|
||||
{
|
||||
HBox toolbar = new HBox();
|
||||
toolbar.getStyleClass().add("jfx-tool-bar-second");
|
||||
JFXDepthManager.setDepth(toolbar, 1);
|
||||
toolbar.setPickOnBounds(false);
|
||||
|
||||
toolbar.getChildren().add(createToolbarButton(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh));
|
||||
toolbar.getChildren().add(createToolbarButton(i18n("datapack.add"), SVG.ADD, skinnable::add));
|
||||
toolbar.getChildren().add(createToolbarButton(i18n("button.remove"), SVG.DELETE, () -> {
|
||||
toolbar.getChildren().add(createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh));
|
||||
toolbar.getChildren().add(createToolbarButton2(i18n("datapack.add"), SVG.ADD, skinnable::add));
|
||||
toolbar.getChildren().add(createToolbarButton2(i18n("button.remove"), SVG.DELETE, () -> {
|
||||
Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), () -> {
|
||||
skinnable.removeSelected(listView.getSelectionModel().getSelectedItems());
|
||||
}, null);
|
||||
}));
|
||||
toolbar.getChildren().add(createToolbarButton(i18n("mods.enable"), SVG.CHECK, () ->
|
||||
toolbar.getChildren().add(createToolbarButton2(i18n("mods.enable"), SVG.CHECK, () ->
|
||||
skinnable.enableSelected(listView.getSelectionModel().getSelectedItems())));
|
||||
toolbar.getChildren().add(createToolbarButton(i18n("mods.disable"), SVG.CLOSE, () ->
|
||||
toolbar.getChildren().add(createToolbarButton2(i18n("mods.disable"), SVG.CLOSE, () ->
|
||||
skinnable.disableSelected(listView.getSelectionModel().getSelectedItems())));
|
||||
root.setTop(toolbar);
|
||||
root.getContent().add(toolbar);
|
||||
}
|
||||
|
||||
{
|
||||
SpinnerPane center = new SpinnerPane();
|
||||
ComponentList.setVgrow(center, Priority.ALWAYS);
|
||||
center.getStyleClass().add("large-spinner-pane");
|
||||
center.loadingProperty().bind(skinnable.loadingProperty());
|
||||
|
||||
listView.setCellFactory(x -> new FloatListCell<DatapackInfoObject>(listView) {
|
||||
JFXCheckBox checkBox = new JFXCheckBox();
|
||||
TwoLineListItem content = new TwoLineListItem();
|
||||
BooleanProperty booleanProperty;
|
||||
|
||||
{
|
||||
Region clippedContainer = (Region)listView.lookup(".clipped-container");
|
||||
HBox container = new HBox(8);
|
||||
container.setPadding(new Insets(0, 0, 0, 6));
|
||||
container.setAlignment(Pos.CENTER_LEFT);
|
||||
pane.getChildren().add(container);
|
||||
pane.setPadding(new Insets(8, 8, 8, 0));
|
||||
if (clippedContainer != null) {
|
||||
maxWidthProperty().bind(clippedContainer.widthProperty());
|
||||
prefWidthProperty().bind(clippedContainer.widthProperty());
|
||||
minWidthProperty().bind(clippedContainer.widthProperty());
|
||||
}
|
||||
|
||||
container.getChildren().setAll(checkBox, content);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateControl(DatapackInfoObject dataItem, boolean empty) {
|
||||
if (empty) return;
|
||||
content.setTitle(dataItem.getTitle());
|
||||
content.setSubtitle(dataItem.getSubtitle());
|
||||
if (booleanProperty != null) {
|
||||
checkBox.selectedProperty().unbindBidirectional(booleanProperty);
|
||||
}
|
||||
checkBox.selectedProperty().bindBidirectional(booleanProperty = dataItem.active);
|
||||
}
|
||||
});
|
||||
Holder<Object> lastCell = new Holder<>();
|
||||
listView.setCellFactory(x -> new DatapackInfoListCell(listView, lastCell));
|
||||
listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||
Bindings.bindContent(listView.getItems(), skinnable.getItems());
|
||||
|
||||
@@ -117,10 +86,11 @@ class DatapackListPageSkin extends SkinBase<DatapackListPage> {
|
||||
ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE);
|
||||
|
||||
center.setContent(listView);
|
||||
root.setCenter(center);
|
||||
root.getContent().add(center);
|
||||
}
|
||||
|
||||
getChildren().setAll(root);
|
||||
pane.getChildren().setAll(root);
|
||||
getChildren().setAll(pane);
|
||||
}
|
||||
|
||||
static class DatapackInfoObject extends RecursiveTreeObject<DatapackInfoObject> {
|
||||
@@ -144,4 +114,36 @@ class DatapackListPageSkin extends SkinBase<DatapackListPage> {
|
||||
return packInfo;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class DatapackInfoListCell extends MDListCell<DatapackInfoObject> {
|
||||
final JFXCheckBox checkBox = new JFXCheckBox();
|
||||
final TwoLineListItem content = new TwoLineListItem();
|
||||
BooleanProperty booleanProperty;
|
||||
|
||||
DatapackInfoListCell(JFXListView<DatapackInfoObject> listView, Holder<Object> lastCell) {
|
||||
super(listView, lastCell);
|
||||
|
||||
HBox container = new HBox(8);
|
||||
container.setPickOnBounds(false);
|
||||
container.setAlignment(Pos.CENTER_LEFT);
|
||||
HBox.setHgrow(content, Priority.ALWAYS);
|
||||
content.setMouseTransparent(true);
|
||||
setSelectable();
|
||||
|
||||
StackPane.setMargin(container, new Insets(8));
|
||||
container.getChildren().setAll(checkBox, content);
|
||||
getContainer().getChildren().setAll(container);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateControl(DatapackInfoObject dataItem, boolean empty) {
|
||||
if (empty) return;
|
||||
content.setTitle(dataItem.getTitle());
|
||||
content.setSubtitle(dataItem.getSubtitle());
|
||||
if (booleanProperty != null) {
|
||||
checkBox.selectedProperty().unbindBidirectional(booleanProperty);
|
||||
}
|
||||
checkBox.selectedProperty().bindBidirectional(booleanProperty = dataItem.active);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ import javafx.scene.layout.*;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.mod.RemoteModRepository;
|
||||
import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
@@ -189,7 +190,9 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
}
|
||||
|
||||
protected String getLocalizedCategory(String category) {
|
||||
return i18n("curse.category." + category);
|
||||
return repository instanceof ModrinthRemoteModRepository
|
||||
? i18n("modrinth.category." + category)
|
||||
: i18n("curse.category." + category);
|
||||
}
|
||||
|
||||
private String getLocalizedCategoryIndent(ModDownloadListPageSkin.CategoryIndented category) {
|
||||
@@ -200,7 +203,11 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
}
|
||||
|
||||
protected String getLocalizedOfficialPage() {
|
||||
return i18n("mods.curseforge");
|
||||
if (repository instanceof ModrinthRemoteModRepository) {
|
||||
return i18n("mods.modrinth");
|
||||
} else {
|
||||
return i18n("mods.curseforge");
|
||||
}
|
||||
}
|
||||
|
||||
protected Profile.ProfileVersion getProfileVersion() {
|
||||
@@ -356,11 +363,12 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
}
|
||||
currentFilterID.set(filterID.get());
|
||||
|
||||
int pageOffset = control.pageOffset.get();
|
||||
getSkinnable().search(gameVersionField.getSelectionModel().getSelectedItem(),
|
||||
Optional.ofNullable(categoryComboBox.getSelectionModel().getSelectedItem())
|
||||
.map(CategoryIndented::getCategory)
|
||||
.orElse(null),
|
||||
control.pageOffset.get(),
|
||||
pageOffset == -1 ? 0 : pageOffset,
|
||||
nameField.getText(),
|
||||
sortComboBox.getSelectionModel().getSelectedItem());
|
||||
};
|
||||
@@ -432,13 +440,15 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
int pageOffset = control.pageOffset.get();
|
||||
int pageCount = control.pageCount.get();
|
||||
|
||||
boolean disablePrevious = pageOffset == 0;
|
||||
boolean disableAll = pageCount >= -1 && pageCount <= 1;
|
||||
|
||||
boolean disablePrevious = disableAll || pageOffset == 0;
|
||||
firstPageButton.setDisable(disablePrevious);
|
||||
previousPageButton.setDisable(disablePrevious);
|
||||
|
||||
boolean disableNext = pageOffset == pageCount - 1;
|
||||
boolean disableNext = disableAll || pageOffset == pageCount - 1;
|
||||
nextPageButton.setDisable(disableNext);
|
||||
lastPageButton.setDisable(disableNext || pageCount == -1);
|
||||
lastPageButton.setDisable(disableNext);
|
||||
};
|
||||
|
||||
FXUtils.onChange(control.pageCount, pageCountN -> {
|
||||
|
||||
@@ -114,6 +114,8 @@ public class InstallerListPage extends ListPageBase<InstallerItem> implements Ve
|
||||
for (LibraryAnalyzer.LibraryMark mark : analyzer) {
|
||||
String libraryId = mark.getLibraryId();
|
||||
String libraryVersion = mark.getLibraryVersion();
|
||||
if ("mcbbs".equals(libraryId))
|
||||
continue;
|
||||
|
||||
// we have done this library above.
|
||||
if (LibraryAnalyzer.LibraryType.fromPatchId(libraryId) != null)
|
||||
|
||||
@@ -50,6 +50,7 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -206,6 +207,7 @@ public final class SchematicsPage extends ListPageBase<SchematicsPage.Item> impl
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (NoSuchFileException ignored) {
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to load schematics in " + dir, e);
|
||||
}
|
||||
|
||||
@@ -315,7 +315,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
txtMaxMemory.textProperty().bindBidirectional(maxMemory, SafeStringConverter.fromInteger());
|
||||
txtMaxMemory.setValidators(new NumberValidator(i18n("input.number"), false));
|
||||
|
||||
lowerBoundPane.getChildren().setAll(label, slider, txtMaxMemory, new Label("MiB"));
|
||||
lowerBoundPane.getChildren().setAll(label, slider, txtMaxMemory, new Label(i18n("settings.memory.unit.mib")));
|
||||
}
|
||||
|
||||
StackPane progressBarPane = new StackPane();
|
||||
|
||||
@@ -37,15 +37,17 @@ public final class WorldBackupTask extends Task<Path> {
|
||||
|
||||
private final World world;
|
||||
private final Path backupsDir;
|
||||
private final boolean needLock;
|
||||
|
||||
public WorldBackupTask(World world, Path backupsDir) {
|
||||
public WorldBackupTask(World world, Path backupsDir, boolean needLock) {
|
||||
this.world = world;
|
||||
this.backupsDir = backupsDir;
|
||||
this.needLock = needLock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
try (FileChannel lockChannel = world.lock()) {
|
||||
try (FileChannel lockChannel = needLock ? world.lock() : null) {
|
||||
Files.createDirectories(backupsDir);
|
||||
String time = LocalDateTime.now().format(WorldBackupsPage.TIME_FORMATTER);
|
||||
String baseName = time + "_" + world.getFileName();
|
||||
|
||||
@@ -18,9 +18,6 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
@@ -41,7 +38,6 @@ import org.jackhuang.hmcl.ui.*;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -65,33 +61,24 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class WorldBackupsPage extends ListPageBase<WorldBackupsPage.BackupInfo> implements DecoratorPage {
|
||||
public final class WorldBackupsPage extends ListPageBase<WorldBackupsPage.BackupInfo> {
|
||||
static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss");
|
||||
|
||||
private final World world;
|
||||
private final Path backupsDir;
|
||||
private final boolean isReadOnly;
|
||||
private final Pattern backupFileNamePattern;
|
||||
private final ObjectProperty<State> state = new SimpleObjectProperty<>();
|
||||
|
||||
public WorldBackupsPage(World world, Path backupsDir) {
|
||||
this.backupsDir = backupsDir;
|
||||
this.world = world;
|
||||
public WorldBackupsPage(WorldManagePage worldManagePage) {
|
||||
this.world = worldManagePage.getWorld();
|
||||
this.backupsDir = worldManagePage.getBackupsDir();
|
||||
this.isReadOnly = worldManagePage.isReadOnly();
|
||||
this.backupFileNamePattern = Pattern.compile("(?<datetime>[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2})_" + Pattern.quote(world.getFileName()) + "( (?<count>[0-9]+))?\\.zip");
|
||||
this.state.set(State.fromTitle(i18n("world.backup.title", world.getWorldName())));
|
||||
loadBackups();
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh() {
|
||||
loadBackups();
|
||||
}
|
||||
|
||||
private void loadBackups() {
|
||||
setLoading(true);
|
||||
Task.supplyAsync(() -> {
|
||||
if (Files.isDirectory(backupsDir)) {
|
||||
@@ -140,7 +127,7 @@ public final class WorldBackupsPage extends ListPageBase<WorldBackupsPage.Backup
|
||||
}
|
||||
|
||||
void createBackup() {
|
||||
Controllers.taskDialog(new WorldBackupTask(world, backupsDir).setName(i18n("world.backup.processing")).thenApplyAsync(path -> {
|
||||
Controllers.taskDialog(new WorldBackupTask(world, backupsDir, false).setName(i18n("world.backup.processing")).thenApplyAsync(path -> {
|
||||
Matcher matcher = backupFileNamePattern.matcher(path.getFileName().toString());
|
||||
if (!matcher.matches()) {
|
||||
throw new AssertionError("Wrong backup file name" + path);
|
||||
@@ -176,7 +163,13 @@ public final class WorldBackupsPage extends ListPageBase<WorldBackupsPage.Backup
|
||||
|
||||
@Override
|
||||
protected List<Node> initializeToolbar(WorldBackupsPage skinnable) {
|
||||
return Arrays.asList(createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh), createToolbarButton2(i18n("world.backup.create.new_one"), SVG.ARCHIVE, skinnable::createBackup));
|
||||
JFXButton createBackup = createToolbarButton2(i18n("world.backup.create.new_one"), SVG.ARCHIVE, skinnable::createBackup);
|
||||
createBackup.setDisable(isReadOnly);
|
||||
|
||||
return Arrays.asList(
|
||||
createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh),
|
||||
createBackup
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,25 +20,27 @@ package org.jackhuang.hmcl.ui.versions;
|
||||
import com.github.steveice10.opennbt.tag.builtin.*;
|
||||
import com.jfoenix.controls.JFXComboBox;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.effect.BoxBlur;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.game.World;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.text.DecimalFormat;
|
||||
import java.time.Instant;
|
||||
@@ -52,38 +54,14 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class WorldInfoPage extends SpinnerPane implements DecoratorPage {
|
||||
public final class WorldInfoPage extends SpinnerPane {
|
||||
private final WorldManagePage worldManagePage;
|
||||
private final World world;
|
||||
private CompoundTag levelDat;
|
||||
|
||||
private final ObjectProperty<State> stateProperty;
|
||||
|
||||
private FileChannel sessionLockChannel;
|
||||
|
||||
@Override
|
||||
public boolean back() {
|
||||
closePage();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closePage() {
|
||||
if (sessionLockChannel != null) {
|
||||
try {
|
||||
sessionLockChannel.close();
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to close session lock channel", e);
|
||||
}
|
||||
|
||||
sessionLockChannel = null;
|
||||
}
|
||||
}
|
||||
|
||||
public WorldInfoPage(World world) {
|
||||
this.world = world;
|
||||
this.stateProperty = new SimpleObjectProperty<>(State.fromTitle(i18n("world.info.title", world.getWorldName())));
|
||||
|
||||
this.getStyleClass().add("gray-background");
|
||||
public WorldInfoPage(WorldManagePage worldManagePage) {
|
||||
this.worldManagePage = worldManagePage;
|
||||
this.world = worldManagePage.getWorld();
|
||||
|
||||
this.setLoading(true);
|
||||
Task.supplyAsync(this::loadWorldInfo)
|
||||
@@ -103,18 +81,9 @@ public final class WorldInfoPage extends SpinnerPane implements DecoratorPage {
|
||||
if (!Files.isDirectory(world.getFile()))
|
||||
throw new IOException("Not a valid world directory");
|
||||
|
||||
try {
|
||||
sessionLockChannel = world.lock();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
|
||||
return world.readLevelDat();
|
||||
}
|
||||
|
||||
private boolean isReadOnly() {
|
||||
return sessionLockChannel == null;
|
||||
}
|
||||
|
||||
private void updateControls() {
|
||||
CompoundTag dataTag = levelDat.get("Data");
|
||||
CompoundTag worldGenSettings = dataTag.get("WorldGenSettings");
|
||||
@@ -161,9 +130,22 @@ public final class WorldInfoPage extends SpinnerPane implements DecoratorPage {
|
||||
|
||||
BorderPane randomSeedPane = new BorderPane();
|
||||
{
|
||||
|
||||
HBox left = new HBox(8);
|
||||
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
|
||||
left.setAlignment(Pos.CENTER_LEFT);
|
||||
randomSeedPane.setLeft(left);
|
||||
|
||||
Label label = new Label(i18n("world.info.random_seed"));
|
||||
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
|
||||
randomSeedPane.setLeft(label);
|
||||
|
||||
SimpleBooleanProperty visibility = new SimpleBooleanProperty();
|
||||
StackPane visibilityButton = new StackPane();
|
||||
visibilityButton.setCursor(Cursor.HAND);
|
||||
FXUtils.setLimitWidth(visibilityButton, 12);
|
||||
FXUtils.setLimitHeight(visibilityButton, 12);
|
||||
FXUtils.onClicked(visibilityButton, () -> visibility.set(!visibility.get()));
|
||||
|
||||
left.getChildren().setAll(label, visibilityButton);
|
||||
|
||||
Label randomSeedLabel = new Label();
|
||||
FXUtils.copyOnDoubleClick(randomSeedLabel);
|
||||
@@ -174,6 +156,14 @@ public final class WorldInfoPage extends SpinnerPane implements DecoratorPage {
|
||||
if (tag instanceof LongTag) {
|
||||
randomSeedLabel.setText(tag.getValue().toString());
|
||||
}
|
||||
|
||||
BoxBlur blur = new BoxBlur();
|
||||
blur.setIterations(3);
|
||||
FXUtils.onChangeAndOperate(visibility, isVisibility -> {
|
||||
SVG icon = isVisibility ? SVG.VISIBILITY : SVG.VISIBILITY_OFF;
|
||||
visibilityButton.getChildren().setAll(icon.createIcon(Theme.blackFill(), 12));
|
||||
randomSeedLabel.setEffect(isVisibility ? null : blur);
|
||||
});
|
||||
}
|
||||
|
||||
BorderPane lastPlayedPane = new BorderPane();
|
||||
@@ -210,7 +200,7 @@ public final class WorldInfoPage extends SpinnerPane implements DecoratorPage {
|
||||
OptionToggleButton allowCheatsButton = new OptionToggleButton();
|
||||
{
|
||||
allowCheatsButton.setTitle(i18n("world.info.allow_cheats"));
|
||||
allowCheatsButton.setDisable(isReadOnly());
|
||||
allowCheatsButton.setDisable(worldManagePage.isReadOnly());
|
||||
Tag tag = dataTag.get("allowCommands");
|
||||
|
||||
if (tag instanceof ByteTag) {
|
||||
@@ -233,7 +223,7 @@ public final class WorldInfoPage extends SpinnerPane implements DecoratorPage {
|
||||
OptionToggleButton generateFeaturesButton = new OptionToggleButton();
|
||||
{
|
||||
generateFeaturesButton.setTitle(i18n("world.info.generate_features"));
|
||||
generateFeaturesButton.setDisable(isReadOnly());
|
||||
generateFeaturesButton.setDisable(worldManagePage.isReadOnly());
|
||||
Tag tag = worldGenSettings != null ? worldGenSettings.get("generate_features") : dataTag.get("MapFeatures");
|
||||
|
||||
if (tag instanceof ByteTag) {
|
||||
@@ -260,7 +250,7 @@ public final class WorldInfoPage extends SpinnerPane implements DecoratorPage {
|
||||
difficultyPane.setLeft(label);
|
||||
|
||||
JFXComboBox<Difficulty> difficultyBox = new JFXComboBox<>(Difficulty.items);
|
||||
difficultyBox.setDisable(isReadOnly());
|
||||
difficultyBox.setDisable(worldManagePage.isReadOnly());
|
||||
BorderPane.setAlignment(difficultyBox, Pos.CENTER_RIGHT);
|
||||
difficultyPane.setRight(difficultyBox);
|
||||
|
||||
@@ -366,7 +356,7 @@ public final class WorldInfoPage extends SpinnerPane implements DecoratorPage {
|
||||
playerGameTypePane.setLeft(label);
|
||||
|
||||
JFXComboBox<GameType> gameTypeBox = new JFXComboBox<>(GameType.items);
|
||||
gameTypeBox.setDisable(isReadOnly());
|
||||
gameTypeBox.setDisable(worldManagePage.isReadOnly());
|
||||
BorderPane.setAlignment(gameTypeBox, Pos.CENTER_RIGHT);
|
||||
playerGameTypePane.setRight(gameTypeBox);
|
||||
|
||||
@@ -397,7 +387,7 @@ public final class WorldInfoPage extends SpinnerPane implements DecoratorPage {
|
||||
healthPane.setLeft(label);
|
||||
|
||||
JFXTextField healthField = new JFXTextField();
|
||||
healthField.setDisable(isReadOnly());
|
||||
healthField.setDisable(worldManagePage.isReadOnly());
|
||||
healthField.setPrefWidth(50);
|
||||
healthField.setAlignment(Pos.CENTER_RIGHT);
|
||||
BorderPane.setAlignment(healthField, Pos.CENTER_RIGHT);
|
||||
@@ -431,7 +421,7 @@ public final class WorldInfoPage extends SpinnerPane implements DecoratorPage {
|
||||
foodLevelPane.setLeft(label);
|
||||
|
||||
JFXTextField foodLevelField = new JFXTextField();
|
||||
foodLevelField.setDisable(isReadOnly());
|
||||
foodLevelField.setDisable(worldManagePage.isReadOnly());
|
||||
foodLevelField.setPrefWidth(50);
|
||||
foodLevelField.setAlignment(Pos.CENTER_RIGHT);
|
||||
BorderPane.setAlignment(foodLevelField, Pos.CENTER_RIGHT);
|
||||
@@ -465,7 +455,7 @@ public final class WorldInfoPage extends SpinnerPane implements DecoratorPage {
|
||||
xpLevelPane.setLeft(label);
|
||||
|
||||
JFXTextField xpLevelField = new JFXTextField();
|
||||
xpLevelField.setDisable(isReadOnly());
|
||||
xpLevelField.setDisable(worldManagePage.isReadOnly());
|
||||
xpLevelField.setPrefWidth(50);
|
||||
xpLevelField.setAlignment(Pos.CENTER_RIGHT);
|
||||
BorderPane.setAlignment(xpLevelField, Pos.CENTER_RIGHT);
|
||||
@@ -510,11 +500,6 @@ public final class WorldInfoPage extends SpinnerPane implements DecoratorPage {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||
return stateProperty;
|
||||
}
|
||||
|
||||
private static final class Dimension {
|
||||
static final Dimension OVERWORLD = new Dimension(null);
|
||||
static final Dimension THE_NETHER = new Dimension(i18n("world.info.dimension.the_nether"));
|
||||
|
||||
@@ -24,7 +24,6 @@ import org.jackhuang.hmcl.game.World;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.wizard.SinglePageWizardProvider;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
@@ -66,20 +65,7 @@ public final class WorldListItem extends Control {
|
||||
FXUtils.openFolder(world.getFile().toFile());
|
||||
}
|
||||
|
||||
public void manageDatapacks() {
|
||||
if (world.getGameVersion() == null || // old game will not write game version to level.dat
|
||||
GameVersionNumber.compare(world.getGameVersion(), "1.13") < 0) {
|
||||
Controllers.dialog(i18n("world.datapack.1_13"));
|
||||
return;
|
||||
}
|
||||
Controllers.navigate(new DatapackListPage(world.getWorldName(), world.getFile()));
|
||||
}
|
||||
|
||||
public void showInfo() {
|
||||
Controllers.navigate(new WorldInfoPage(world));
|
||||
}
|
||||
|
||||
public void showBackupPage() {
|
||||
Controllers.navigate(new WorldBackupsPage(world, backupsDir));
|
||||
public void showManagePage() {
|
||||
Controllers.navigate(new WorldManagePage(world, backupsDir));
|
||||
}
|
||||
}
|
||||
@@ -18,10 +18,12 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXPopup;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
@@ -29,8 +31,9 @@ import org.jackhuang.hmcl.game.World;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.util.ChunkBaseApp;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
@@ -40,12 +43,14 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class WorldListItemSkin extends SkinBase<WorldListItem> {
|
||||
|
||||
private final BorderPane root;
|
||||
|
||||
public WorldListItemSkin(WorldListItem skinnable) {
|
||||
super(skinnable);
|
||||
|
||||
World world = skinnable.getWorld();
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
root = new BorderPane();
|
||||
root.getStyleClass().add("md-list-cell");
|
||||
root.setPadding(new Insets(8));
|
||||
|
||||
@@ -64,6 +69,7 @@ public final class WorldListItemSkin extends SkinBase<WorldListItem> {
|
||||
{
|
||||
TwoLineListItem item = new TwoLineListItem();
|
||||
root.setCenter(item);
|
||||
item.setMouseTransparent(true);
|
||||
if (world.getWorldName() != null)
|
||||
item.setTitle(parseColorEscapes(world.getWorldName()));
|
||||
item.setSubtitle(i18n("world.datetime", formatDateTime(Instant.ofEpochMilli(world.getLastPlayed())), world.getGameVersion() == null ? i18n("message.unknown") : world.getGameVersion()));
|
||||
@@ -79,42 +85,58 @@ public final class WorldListItemSkin extends SkinBase<WorldListItem> {
|
||||
root.setRight(right);
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
JFXButton btnReveal = new JFXButton();
|
||||
right.getChildren().add(btnReveal);
|
||||
FXUtils.installFastTooltip(btnReveal, i18n("reveal.in_file_manager"));
|
||||
btnReveal.getStyleClass().add("toggle-icon4");
|
||||
btnReveal.setGraphic(SVG.FOLDER_OPEN.createIcon(Theme.blackFill(), -1));
|
||||
btnReveal.setOnAction(event -> skinnable.reveal());
|
||||
|
||||
JFXButton btnExport = new JFXButton();
|
||||
right.getChildren().add(btnExport);
|
||||
FXUtils.installFastTooltip(btnExport, i18n("world.export"));
|
||||
btnExport.getStyleClass().add("toggle-icon4");
|
||||
btnExport.setGraphic(SVG.OUTPUT.createIcon(Theme.blackFill(), -1));
|
||||
btnExport.setOnAction(event -> skinnable.export());
|
||||
|
||||
JFXButton btnBackup = new JFXButton();
|
||||
right.getChildren().add(btnBackup);
|
||||
FXUtils.installFastTooltip(btnBackup, i18n("world.backup"));
|
||||
btnBackup.getStyleClass().add("toggle-icon4");
|
||||
btnBackup.setGraphic(SVG.ARCHIVE.createIcon(Theme.blackFill(), -1));
|
||||
btnBackup.setOnAction(event -> skinnable.showBackupPage());
|
||||
|
||||
JFXButton btnDatapack = new JFXButton();
|
||||
right.getChildren().add(btnDatapack);
|
||||
FXUtils.installFastTooltip(btnDatapack, i18n("world.datapack"));
|
||||
btnDatapack.getStyleClass().add("toggle-icon4");
|
||||
btnDatapack.setGraphic(SVG.EXTENSION.createIcon(Theme.blackFill(), -1));
|
||||
btnDatapack.setOnAction(event -> skinnable.manageDatapacks());
|
||||
|
||||
JFXButton btnInfo = new JFXButton();
|
||||
right.getChildren().add(btnInfo);
|
||||
FXUtils.installFastTooltip(btnInfo, i18n("world.info"));
|
||||
btnInfo.getStyleClass().add("toggle-icon4");
|
||||
btnInfo.setGraphic(SVG.INFO.createIcon(Theme.blackFill(), -1));
|
||||
btnInfo.setOnAction(event -> skinnable.showInfo());
|
||||
JFXButton btnMore = new JFXButton();
|
||||
right.getChildren().add(btnMore);
|
||||
btnMore.getStyleClass().add("toggle-icon4");
|
||||
btnMore.setGraphic(SVG.MORE_VERT.createIcon(Theme.blackFill(), -1));
|
||||
btnMore.setOnAction(event -> showPopupMenu(JFXPopup.PopupHPosition.RIGHT, 0, root.getHeight()));
|
||||
}
|
||||
|
||||
getChildren().setAll(new RipplerContainer(root));
|
||||
RipplerContainer container = new RipplerContainer(root);
|
||||
container.setOnMouseClicked(event -> {
|
||||
if (event.getClickCount() != 1)
|
||||
return;
|
||||
|
||||
if (event.getButton() == MouseButton.PRIMARY)
|
||||
skinnable.showManagePage();
|
||||
else if (event.getButton() == MouseButton.SECONDARY)
|
||||
showPopupMenu(JFXPopup.PopupHPosition.LEFT, event.getX(), event.getY());
|
||||
});
|
||||
|
||||
getChildren().setAll(container);
|
||||
}
|
||||
|
||||
// Popup Menu
|
||||
|
||||
public void showPopupMenu(JFXPopup.PopupHPosition hPosition, double initOffsetX, double initOffsetY) {
|
||||
PopupMenu popupMenu = new PopupMenu();
|
||||
JFXPopup popup = new JFXPopup(popupMenu);
|
||||
|
||||
WorldListItem item = getSkinnable();
|
||||
World world = item.getWorld();
|
||||
|
||||
popupMenu.getContent().addAll(
|
||||
new IconedMenuItem(SVG.SETTINGS, i18n("world.manage.button"), item::showManagePage, popup));
|
||||
|
||||
if (ChunkBaseApp.isSupported(world)) {
|
||||
popupMenu.getContent().addAll(
|
||||
new MenuSeparator(),
|
||||
new IconedMenuItem(SVG.EXPLORE, i18n("world.chunkbase.seed_map"), () -> ChunkBaseApp.openSeedMap(world), popup),
|
||||
new IconedMenuItem(SVG.VISIBILITY, i18n("world.chunkbase.stronghold"), () -> ChunkBaseApp.openStrongholdFinder(world), popup),
|
||||
new IconedMenuItem(SVG.FORT, i18n("world.chunkbase.nether_fortress"), () -> ChunkBaseApp.openNetherFortressFinder(world), popup)
|
||||
);
|
||||
|
||||
if (GameVersionNumber.compare(world.getGameVersion(), "1.13") >= 0) {
|
||||
popupMenu.getContent().add(new IconedMenuItem(SVG.LOCATION_CITY, i18n("world.chunkbase.end_city"),
|
||||
() -> ChunkBaseApp.openEndCityFinder(world), popup));
|
||||
}
|
||||
}
|
||||
|
||||
popupMenu.getContent().addAll(
|
||||
new MenuSeparator(),
|
||||
new IconedMenuItem(SVG.OUTPUT, i18n("world.export"), item::export, popup),
|
||||
new IconedMenuItem(SVG.FOLDER_OPEN, i18n("folder.world"), item::reveal, popup));
|
||||
|
||||
popup.show(root, JFXPopup.PopupVPosition.TOP, hPosition, initOffsetX, initOffsetY);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.controls.JFXPopup;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.game.World;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.ChunkBaseApp;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class WorldManagePage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
|
||||
private final ObjectProperty<State> state;
|
||||
private final World world;
|
||||
private final Path backupsDir;
|
||||
|
||||
private final TabHeader header;
|
||||
private final TabHeader.Tab<WorldInfoPage> worldInfoTab = new TabHeader.Tab<>("worldInfoPage");
|
||||
private final TabHeader.Tab<WorldBackupsPage> worldBackupsTab = new TabHeader.Tab<>("worldBackupsPage");
|
||||
private final TabHeader.Tab<DatapackListPage> datapackTab = new TabHeader.Tab<>("datapackListPage");
|
||||
|
||||
private final TransitionPane transitionPane = new TransitionPane();
|
||||
|
||||
private FileChannel sessionLockChannel;
|
||||
|
||||
public WorldManagePage(World world, Path backupsDir) {
|
||||
this.world = world;
|
||||
this.backupsDir = backupsDir;
|
||||
|
||||
this.state = new SimpleObjectProperty<>(State.fromTitle(i18n("world.manage.title", world.getWorldName())));
|
||||
this.header = new TabHeader(worldInfoTab, worldBackupsTab);
|
||||
|
||||
if (world.getGameVersion() != null && // old game will not write game version to level.dat
|
||||
GameVersionNumber.compare(world.getGameVersion(), "1.13") >= 0) {
|
||||
header.getTabs().add(datapackTab);
|
||||
}
|
||||
|
||||
worldInfoTab.setNodeSupplier(() -> new WorldInfoPage(this));
|
||||
worldBackupsTab.setNodeSupplier(() -> new WorldBackupsPage(this));
|
||||
datapackTab.setNodeSupplier(() -> new DatapackListPage(this));
|
||||
|
||||
header.select(worldInfoTab);
|
||||
transitionPane.setContent(worldInfoTab.getNode(), ContainerAnimations.NONE);
|
||||
FXUtils.onChange(header.getSelectionModel().selectedItemProperty(), newValue ->
|
||||
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE));
|
||||
setCenter(transitionPane);
|
||||
|
||||
BorderPane left = new BorderPane();
|
||||
FXUtils.setLimitWidth(left, 200);
|
||||
VBox.setVgrow(left, Priority.ALWAYS);
|
||||
setLeft(left);
|
||||
|
||||
AdvancedListBox sideBar = new AdvancedListBox()
|
||||
.addNavigationDrawerTab(header, worldInfoTab, i18n("world.info"), SVG.INFO)
|
||||
.addNavigationDrawerTab(header, worldBackupsTab, i18n("world.backup"), SVG.ARCHIVE)
|
||||
.addNavigationDrawerTab(header, datapackTab, i18n("world.datapack"), SVG.EXTENSION);
|
||||
left.setTop(sideBar);
|
||||
|
||||
AdvancedListBox toolbar = new AdvancedListBox();
|
||||
if (ChunkBaseApp.isSupported(world)) {
|
||||
PopupMenu popupMenu = new PopupMenu();
|
||||
JFXPopup popup = new JFXPopup(popupMenu);
|
||||
|
||||
popupMenu.getContent().addAll(
|
||||
new IconedMenuItem(SVG.EXPLORE, i18n("world.chunkbase.seed_map"), () -> ChunkBaseApp.openSeedMap(world), popup),
|
||||
new IconedMenuItem(SVG.VISIBILITY, i18n("world.chunkbase.stronghold"), () -> ChunkBaseApp.openStrongholdFinder(world), popup),
|
||||
new IconedMenuItem(SVG.FORT, i18n("world.chunkbase.nether_fortress"), () -> ChunkBaseApp.openNetherFortressFinder(world), popup)
|
||||
);
|
||||
|
||||
if (GameVersionNumber.compare(world.getGameVersion(), "1.13") >= 0) {
|
||||
popupMenu.getContent().add(
|
||||
new IconedMenuItem(SVG.LOCATION_CITY, i18n("world.chunkbase.end_city"), () -> ChunkBaseApp.openEndCityFinder(world), popup));
|
||||
}
|
||||
|
||||
toolbar.addNavigationDrawerItem(i18n("world.chunkbase"), SVG.EXPLORE, null, chunkBaseMenuItem ->
|
||||
chunkBaseMenuItem.setOnAction(e ->
|
||||
popup.show(chunkBaseMenuItem,
|
||||
JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.LEFT,
|
||||
chunkBaseMenuItem.getWidth(), 0)));
|
||||
}
|
||||
toolbar.addNavigationDrawerItem(i18n("settings.game.exploration"), SVG.FOLDER_OPEN, () -> FXUtils.openFolder(world.getFile().toFile()), null);
|
||||
|
||||
BorderPane.setMargin(toolbar, new Insets(0, 0, 12, 0));
|
||||
left.setBottom(toolbar);
|
||||
|
||||
// Does it need to be done in the background?
|
||||
try {
|
||||
sessionLockChannel = world.lock();
|
||||
LOG.info("Acquired lock on world " + world.getFileName());
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public World getWorld() {
|
||||
return world;
|
||||
}
|
||||
|
||||
public Path getBackupsDir() {
|
||||
return backupsDir;
|
||||
}
|
||||
|
||||
public boolean isReadOnly() {
|
||||
return sessionLockChannel == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean back() {
|
||||
closePage();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closePage() {
|
||||
if (sessionLockChannel != null) {
|
||||
try {
|
||||
sessionLockChannel.close();
|
||||
LOG.info("Releases the lock on world " + world.getFileName());
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to close session lock channel", e);
|
||||
}
|
||||
|
||||
sessionLockChannel = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.UpgradeDialog;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.ui.SwingUtils;
|
||||
@@ -102,6 +103,16 @@ public final class UpdateHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
if (JavaRuntime.CURRENT_VERSION < Metadata.MINIMUM_SUPPORTED_JAVA_VERSION) {
|
||||
MessageDialogPane.Builder builder = new MessageDialogPane.Builder(i18n("fatal.deprecated_java_version.update"), i18n("message.error"), MessageType.ERROR);
|
||||
String downloadLink = Metadata.getSuggestedJavaDownloadLink();
|
||||
if (downloadLink != null)
|
||||
builder.addHyperLink(i18n("fatal.deprecated_java_version.download_link", 21), downloadLink);
|
||||
builder.ok(null);
|
||||
Controllers.dialog(builder.build());
|
||||
return;
|
||||
}
|
||||
|
||||
Controllers.dialog(new UpgradeDialog(version, () -> {
|
||||
Path downloaded;
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 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 org.jackhuang.hmcl.game.World;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public final class ChunkBaseApp {
|
||||
private static final String CHUNK_BASE_URL = "https://www.chunkbase.com";
|
||||
|
||||
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"
|
||||
};
|
||||
|
||||
public static final String[] STRONGHOLD_FINDER_GAME_VERSIONS = {
|
||||
"1.20", "1.19.3", "1.19", "1.18", "1.16", "1.13", "1.9", "1.7"
|
||||
};
|
||||
|
||||
public static final String[] NETHER_FORTRESS_GAME_VERSIONS = {
|
||||
"1.18", "1.16", "1.7"
|
||||
};
|
||||
|
||||
public static final String[] END_CITY_GAME_VERSIONS = {
|
||||
"1.19", "1.13"
|
||||
};
|
||||
|
||||
public static boolean isSupported(@NotNull World world) {
|
||||
return world.getSeed() != null && world.getGameVersion() != null &&
|
||||
GameVersionNumber.asGameVersion(world.getGameVersion()).compareTo(MIN_GAME_VERSION) >= 0;
|
||||
}
|
||||
|
||||
public static ChunkBaseApp newBuilder(String app, long seed) {
|
||||
return new ChunkBaseApp(new StringBuilder(CHUNK_BASE_URL).append("/apps/").append(app).append("#seed=").append(seed));
|
||||
}
|
||||
|
||||
public static void openSeedMap(World world) {
|
||||
assert isSupported(world);
|
||||
|
||||
newBuilder("seed-map", Objects.requireNonNull(world.getSeed()))
|
||||
.addPlatform(GameVersionNumber.asGameVersion(world.getGameVersion()), world.isLargeBiomes(), SEED_MAP_GAME_VERSIONS)
|
||||
.open();
|
||||
}
|
||||
|
||||
public static void openStrongholdFinder(World world) {
|
||||
assert isSupported(world);
|
||||
|
||||
newBuilder("stronghold-finder", Objects.requireNonNull(world.getSeed()))
|
||||
.addPlatform(GameVersionNumber.asGameVersion(world.getGameVersion()), world.isLargeBiomes(), STRONGHOLD_FINDER_GAME_VERSIONS)
|
||||
.open();
|
||||
}
|
||||
|
||||
public static void openNetherFortressFinder(World world) {
|
||||
assert isSupported(world);
|
||||
|
||||
newBuilder("nether-fortress-finder", Objects.requireNonNull(world.getSeed()))
|
||||
.addPlatform(GameVersionNumber.asGameVersion(world.getGameVersion()), false, NETHER_FORTRESS_GAME_VERSIONS)
|
||||
.open();
|
||||
}
|
||||
|
||||
public static void openEndCityFinder(World world) {
|
||||
assert isSupported(world);
|
||||
|
||||
newBuilder("endcity-finder", Objects.requireNonNull(world.getSeed()))
|
||||
.addPlatform(GameVersionNumber.asGameVersion(world.getGameVersion()), false, END_CITY_GAME_VERSIONS)
|
||||
.open();
|
||||
}
|
||||
|
||||
private final StringBuilder builder;
|
||||
|
||||
private ChunkBaseApp(StringBuilder builder) {
|
||||
this.builder = builder;
|
||||
}
|
||||
|
||||
public ChunkBaseApp add(String key, String value) {
|
||||
builder.append('&').append(key).append('=').append(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ChunkBaseApp addPlatform(GameVersionNumber gameVersion, boolean largeBiomes, String[] versionList) {
|
||||
String version = null;
|
||||
for (String candidateVersion : versionList) {
|
||||
if (gameVersion.compareTo(candidateVersion) >= 0) {
|
||||
version = candidateVersion;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (version == null) {
|
||||
version = versionList[versionList.length - 1]; // Use the last version if no suitable version found
|
||||
}
|
||||
|
||||
add("platform", "java_" + version.replace('.', '_') + (largeBiomes ? "_lb" : ""));
|
||||
return this;
|
||||
}
|
||||
|
||||
public void open() {
|
||||
FXUtils.openLink(builder.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -332,6 +332,7 @@ download=Download
|
||||
download.hint=Install games and modpacks or download mods, resource packs, and worlds.
|
||||
download.code.404=File "%s" not found on the remote server.
|
||||
download.content=Addons
|
||||
download.shader=Shaders
|
||||
download.curseforge.unavailable=This HMCL build does not support access to CurseForge. Please use the official build to access CurseForge.
|
||||
download.existing=The file cannot be saved because it already exists. You can click "Save As" to save the file elsewhere.
|
||||
download.external_link=Visit Download Website
|
||||
@@ -402,6 +403,10 @@ fatal.deprecated_java_version=HMCL will require Java 11 or later to run in the f
|
||||
It is recommended to install the latest version of Java to ensure that HMCL works properly.\n\
|
||||
\n\
|
||||
You can continue to keep the old version of Java. HMCL can recognize and manage multiple Java versions and will automatically select the appropriate Java for you based on the game version.
|
||||
fatal.deprecated_java_version.update=HMCL will require Java 11 or later to run in the future. Please install the latest version of Java to ensure that HMCL can complete the upgrade.\n\
|
||||
\n\
|
||||
You can continue to keep the old version of Java.\
|
||||
HMCL can recognize and manage multiple Java versions and will automatically select the appropriate Java for you based on the game version.
|
||||
fatal.deprecated_java_version.download_link=Download Java %d
|
||||
fatal.samba=If you opened Hello Minecraft! Launcher from a Samba network drive, some features might not be working. Please try updating your Java or moving the launcher to another directory.
|
||||
fatal.illegal_char=Your user path contains an illegal character "=". You will not be able to use authlib-injector or change the skin of your offline account.
|
||||
@@ -437,6 +442,7 @@ folder.shaderpacks=Shader Packs
|
||||
folder.saves=Saves
|
||||
folder.schematics=Schematics
|
||||
folder.screenshots=Screenshots
|
||||
folder.world=World Directory
|
||||
|
||||
game=Games
|
||||
game.crash.feedback=<b>Please do not share screenshots of this window with others!</b> If you ask for help from others, please click <b>"Export Crash Logs"</b> and send the exported file to others for analysis.
|
||||
@@ -932,11 +938,16 @@ modpack.wizard.step.initialization.warning=Before making a modpack, please make
|
||||
modpack.wizard.step.initialization.server=Click here for more information on how to make a server modpack that can be automatically updated.
|
||||
|
||||
modrinth.category.adventure=Adventure
|
||||
modrinth.category.atmosphere=Atmosphere
|
||||
modrinth.category.audio=Audio
|
||||
modrinth.category.blocks=Blocks
|
||||
modrinth.category.bloom=Bloom
|
||||
modrinth.category.bukkit=Bukkit
|
||||
modrinth.category.bungeecord=BungeeCord
|
||||
modrinth.category.canvas=Canvas
|
||||
modrinth.category.cartoon=Cartoon
|
||||
modrinth.category.challenging=Challenging
|
||||
modrinth.category.colored-lighting=Colored Lighting
|
||||
modrinth.category.core-shaders=Core Shaders
|
||||
modrinth.category.combat=Combat
|
||||
modrinth.category.cursed=Cursed
|
||||
@@ -946,19 +957,25 @@ modrinth.category.entities=Entities
|
||||
modrinth.category.environment=Environment
|
||||
modrinth.category.equipment=Equipment
|
||||
modrinth.category.fabric=Fabric
|
||||
modrinth.category.fantasy=Fantasy
|
||||
modrinth.category.foliage=Foliage
|
||||
modrinth.category.fonts=Fonts
|
||||
modrinth.category.food=Food
|
||||
modrinth.category.forge=Forge
|
||||
modrinth.category.game-mechanics=Game Mechanics
|
||||
modrinth.category.gui=GUI
|
||||
modrinth.category.high=High
|
||||
modrinth.category.iris=Iris
|
||||
modrinth.category.items=Items
|
||||
modrinth.category.kitchen-sink=Kitchen-Sink
|
||||
modrinth.category.library=Library
|
||||
modrinth.category.lightweight=Lightweight
|
||||
modrinth.category.liteloader=LiteLoader
|
||||
modrinth.category.locale=Locale
|
||||
modrinth.category.low=Low
|
||||
modrinth.category.magic=Magic
|
||||
modrinth.category.management=Management
|
||||
modrinth.category.medium=Medium
|
||||
modrinth.category.minecraft=Minecraft
|
||||
modrinth.category.minigame=Minigame
|
||||
modrinth.category.misc=Misc
|
||||
@@ -968,13 +985,21 @@ modrinth.category.models=Models
|
||||
modrinth.category.modloader=Modloader
|
||||
modrinth.category.multiplayer=Multiplayer
|
||||
modrinth.category.neoforge=NeoForge
|
||||
modrinth.category.optifine=OptiFine
|
||||
modrinth.category.optimization=Optimization
|
||||
modrinth.category.paper=Paper
|
||||
modrinth.category.path-tracing=Path Tracing
|
||||
modrinth.category.pbr=PBR
|
||||
modrinth.category.potato=Potato
|
||||
modrinth.category.purpur=Purpur
|
||||
modrinth.category.quests=Quests
|
||||
modrinth.category.quilt=Quilt
|
||||
modrinth.category.realistic=Realistic
|
||||
modrinth.category.reflections=Reflections
|
||||
modrinth.category.rift=Rift
|
||||
modrinth.category.screenshot=Screenshot
|
||||
modrinth.category.semi-realistic=Semi-realistic
|
||||
modrinth.category.shadows=Shadows
|
||||
modrinth.category.simplistic=Simplistic
|
||||
modrinth.category.social=Social
|
||||
modrinth.category.spigot=Spigot
|
||||
@@ -985,6 +1010,7 @@ modrinth.category.themed=Themed
|
||||
modrinth.category.transportation=Transportation
|
||||
modrinth.category.tweaks=Tweaks
|
||||
modrinth.category.utility=Utility
|
||||
modrinth.category.vanilla=Vanilla
|
||||
modrinth.category.vanilla-like=Vanilla-like
|
||||
modrinth.category.velocity=Velocity
|
||||
modrinth.category.waterfall=Waterfall
|
||||
@@ -1061,7 +1087,7 @@ datapack.title=World [%s] - Datapacks
|
||||
|
||||
web.failed=Failed to load page
|
||||
web.open_in_browser=Do you want to open this address in a browser:\n%s
|
||||
web.view_in_browser=View in browser
|
||||
web.view_in_browser=View all in browser
|
||||
|
||||
world=Worlds
|
||||
world.add=Add World
|
||||
@@ -1072,9 +1098,12 @@ world.backup.create.locked=The world is currently in use. Please close the game
|
||||
world.backup.create.success=Successfully created a new backup: %s
|
||||
world.backup.delete=Delete this backup
|
||||
world.backup.processing=Backing up ...
|
||||
world.backup.title=World [%s] - Backups
|
||||
world.datapack=Manage Datapacks
|
||||
world.datapack.1_13=Only Minecraft 1.13 or later supports datapacks.
|
||||
world.chunkbase=Chunk Base
|
||||
world.chunkbase.end_city=End City
|
||||
world.chunkbase.seed_map=Seed Map
|
||||
world.chunkbase.stronghold=Stronghold
|
||||
world.chunkbase.nether_fortress=Nether Fortress
|
||||
world.datapack=Datapacks
|
||||
world.datetime=Last played on %s
|
||||
world.download=Download World
|
||||
world.download.title=Download World - %1s
|
||||
@@ -1089,7 +1118,6 @@ world.import.choose=Choose world archive you want to import
|
||||
world.import.failed=Failed to import this world: %s
|
||||
world.import.invalid=Failed to parse the world.
|
||||
world.info=World Information
|
||||
world.info.title=World [%s] - Information
|
||||
world.info.basic=Basic Information
|
||||
world.info.allow_cheats=Allow Commands/Cheats
|
||||
world.info.dimension.the_nether=The Nether
|
||||
@@ -1120,6 +1148,8 @@ world.info.time=Game Time
|
||||
world.info.time.format=%s days
|
||||
world.locked=In use
|
||||
world.manage=Worlds
|
||||
world.manage.button=World Management
|
||||
world.manage.title=World - %s
|
||||
world.name=World Name
|
||||
world.name.enter=Enter the world name
|
||||
world.show_all=Show All
|
||||
@@ -1331,6 +1361,7 @@ settings.memory.allocate.manual=%1$.1f GiB Allocated
|
||||
settings.memory.allocate.manual.exceeded=%1$.1f GiB Allocated (%3$.1f GiB Available)
|
||||
settings.memory.auto_allocate=Automatically Allocate
|
||||
settings.memory.lower_bound=Minimum Memory
|
||||
settings.memory.unit.mib=MiB
|
||||
settings.memory.used_per_total=%1$.1f GiB Used / %2$.1f GiB Total
|
||||
settings.physical_memory=Physical Memory Size
|
||||
settings.show_log=Show Logs
|
||||
|
||||
@@ -333,6 +333,7 @@ download=Descargar
|
||||
download.hint=Instalar juegos y modpacks o descargar mods, paquetes de recursos y mundos.
|
||||
download.code.404=Archivo no encontrado en el servidor remoto: %s
|
||||
download.content=Complementos
|
||||
download.shader=Sombreadores
|
||||
download.curseforge.unavailable=Esta versión de HMCL no permite acceder a CurseForge. Utilice la versión oficial para acceder a CurseForge.
|
||||
download.existing=El archivo no se puede guardar porque ya existe. Puedes hacer clic en «Guardar como» para guardar el archivo en otro lugar.
|
||||
download.external_link=Abrir sitio web
|
||||
@@ -403,7 +404,12 @@ fatal.deprecated_java_version=HMCL requerirá Java 11 o posterior para funcionar
|
||||
\n\
|
||||
Se recomienda instalar la última versión de Java para garantizar el correcto funcionamiento de HMCL.\n\
|
||||
\n\
|
||||
Puede seguir manteniendo la versión antigua de Java. HMCL puede reconocer y gestionar varias versiones de Java y elegirá automáticamente la adecuada para usted en función de la versión del juego.
|
||||
Puedes conservar la versión antigua de Java. HMCL puede reconocer y gestionar varias versiones de Java y elegirá automáticamente la adecuada para usted en función de la versión del juego.
|
||||
fatal.deprecated_java_version.update=HMCL requerirá Java 11 o posterior para funcionar en el futuro. Por favor, instalar la última versión de Java para asegurarse de que el lanzador puede completar la actualización.\n\
|
||||
\n\
|
||||
Puedes conservar la versión antigua de Java.\
|
||||
HMCL puede reconocer y gestionar varias versiones de Java \
|
||||
y elegirá automáticamente la adecuada para usted en función de la versión del juego.
|
||||
fatal.deprecated_java_version.download_link=Descargar Java %d
|
||||
fatal.samba=Si ha abierto Hello Minecraft! Launcher desde una unidad de red Samba, es posible que algunas funciones no funcionen. Intenta actualizar tu Java o mover el launcher a otro directorio.
|
||||
fatal.illegal_char=Su ruta de usuario contiene un carácter ilegal «=», por lo que algunas características podrían no funcionar correctamente.\n\
|
||||
@@ -438,7 +444,9 @@ folder.mod=Mods
|
||||
folder.resourcepacks=Paquetes de recursos
|
||||
folder.shaderpacks=Paquetes de sombreados
|
||||
folder.saves=Mundos
|
||||
folder.schematics=Esquemas
|
||||
folder.screenshots=Capturas de pantalla
|
||||
folder.world=Directorio del mundo
|
||||
|
||||
game=Juegos
|
||||
game.crash.feedback=<b>¡Por favor, no comparta capturas de pantalla de esta interfaz con otras personas!</b> Si pide ayuda a otras personas, haga clic en <b>Exportar registros de colgado</b> y envíe el archivo exportado a otras personas para que lo analicen.
|
||||
@@ -936,11 +944,16 @@ modpack.wizard.step.initialization.warning=Antes de hacer un modpack, por favor
|
||||
modpack.wizard.step.initialization.server=Haga clic aquí para ver más tutoriales para hacer un modpack de servidor que se pueda actualizar automáticamente.
|
||||
|
||||
modrinth.category.adventure=Aventura
|
||||
modrinth.category.atmosphere=Atmósfera
|
||||
modrinth.category.audio=Audio
|
||||
modrinth.category.blocks=Bloqueos
|
||||
modrinth.category.bloom=Resplandor
|
||||
modrinth.category.bukkit=Bukkit
|
||||
modrinth.category.bungeecord=BungeeCord
|
||||
modrinth.category.canvas=Canvas
|
||||
modrinth.category.cartoon=Caricatura
|
||||
modrinth.category.challenging=Desafío
|
||||
modrinth.category.colored-lighting=Iluminación de color
|
||||
modrinth.category.core-shaders=Sombreadores de núcleo
|
||||
modrinth.category.combat=Combate
|
||||
modrinth.category.cursed=Maldición
|
||||
@@ -950,19 +963,25 @@ modrinth.category.entities=Entidades
|
||||
modrinth.category.environment=Medio ambiente
|
||||
modrinth.category.equipment=Equipo
|
||||
modrinth.category.fabric=Fabric
|
||||
modrinth.category.fantasy=Fantasía
|
||||
modrinth.category.foliage=Vegetación
|
||||
modrinth.category.fonts=Fonts
|
||||
modrinth.category.food=Alimentos
|
||||
modrinth.category.forge=Forge
|
||||
modrinth.category.game-mechanics=Mecánica de juego
|
||||
modrinth.category.gui=GUI
|
||||
modrinth.category.high=Alto
|
||||
modrinth.category.iris=Iris
|
||||
modrinth.category.items=Objetos
|
||||
modrinth.category.kitchen-sink=Fregadero de cocina
|
||||
modrinth.category.library=Biblioteca
|
||||
modrinth.category.lightweight=Peso ligero
|
||||
modrinth.category.liteloader=LiteLoader
|
||||
modrinth.category.locale=Locale
|
||||
modrinth.category.low=Bajo
|
||||
modrinth.category.magic=Magia
|
||||
modrinth.category.management=Gestión
|
||||
modrinth.category.medium=Medio
|
||||
modrinth.category.minecraft=Minecraft
|
||||
modrinth.category.minigame=Minijuego
|
||||
modrinth.category.misc=Misceláneo
|
||||
@@ -972,13 +991,21 @@ modrinth.category.models=Modelos
|
||||
modrinth.category.modloader=Cargador de mods
|
||||
modrinth.category.multiplayer=Multijugador
|
||||
modrinth.category.neoforge=NeoForge
|
||||
modrinth.category.optifine=OptiFine
|
||||
modrinth.category.optimization=Optimización
|
||||
modrinth.category.paper=Paper
|
||||
modrinth.category.path-tracing=Trazado de rutas
|
||||
modrinth.category.pbr=PBR
|
||||
modrinth.category.potato=Mínimo
|
||||
modrinth.category.purpur=Purpur
|
||||
modrinth.category.quests=Misiones
|
||||
modrinth.category.quilt=Quilt
|
||||
modrinth.category.realistic=Realista
|
||||
modrinth.category.reflections=Reflexiones
|
||||
modrinth.category.rift=Rift
|
||||
modrinth.category.screenshot=Extremo
|
||||
modrinth.category.semi-realistic=Semirrealista
|
||||
modrinth.category.shadows=Sombras
|
||||
modrinth.category.simplistic=Simplista
|
||||
modrinth.category.social=Social
|
||||
modrinth.category.spigot=Spigot
|
||||
@@ -989,6 +1016,7 @@ modrinth.category.themed=Temática
|
||||
modrinth.category.transportation=Transporte
|
||||
modrinth.category.tweaks=Ajustes
|
||||
modrinth.category.utility=Utilidad
|
||||
modrinth.category.vanilla=Vainilla
|
||||
modrinth.category.vanilla-like=Similar a la vainilla
|
||||
modrinth.category.velocity=Velocity
|
||||
modrinth.category.waterfall=Waterfall
|
||||
@@ -1076,9 +1104,12 @@ world.backup.create.locked=El mundo está actualmente en uso. Por favor, cierra
|
||||
world.backup.create.success=Creada con éxito una nueva copia de seguridad: %s
|
||||
world.backup.delete=Eliminar esta copia de seguridad
|
||||
world.backup.processing=Creando nueva copia de seguridad ...
|
||||
world.backup.title=Mundo [%s] - Copias de seguridad
|
||||
world.datapack=Gestionar paquetes de datos
|
||||
world.datapack.1_13=Sólo Minecraft 1.13 o posterior soporta paquetes de datos.
|
||||
world.chunkbase=Chunk Base
|
||||
world.chunkbase.end_city=Ciudad del End
|
||||
world.chunkbase.seed_map=Vista previa de la generación mundial
|
||||
world.chunkbase.stronghold=Fortaleza
|
||||
world.chunkbase.nether_fortress=Fortaleza del Nether
|
||||
world.datapack=Paquetes de datos
|
||||
world.datetime=Jugado por última vez en %s
|
||||
world.download=Descargar Mundo
|
||||
world.download.title=Descargar mundo - %1s
|
||||
@@ -1093,7 +1124,6 @@ world.import.choose=Elija el archivo de mundo que desea importar
|
||||
world.import.failed=No se ha podido importar este mundo: %s
|
||||
world.import.invalid=No se ha podido analizar el mundo.
|
||||
world.info=Información del mundo
|
||||
world.info.title=Mundo [%s] - Información
|
||||
world.info.basic=Información básica
|
||||
world.info.allow_cheats=Permitir comandos/trucos
|
||||
world.info.dimension.the_nether=El Nether
|
||||
@@ -1124,6 +1154,8 @@ world.info.time=Tiempo de juego
|
||||
world.info.time.format=%s días
|
||||
world.locked=En uso
|
||||
world.manage=Mundos
|
||||
world.manage.button=Administrar
|
||||
world.manage.title=Mundo - %s
|
||||
world.name=Nombre del mundo
|
||||
world.name.enter=Introducir el nombre del mundo
|
||||
world.show_all=Mostrar todo
|
||||
@@ -1334,6 +1366,7 @@ settings.memory.allocate.manual=%1$.1f GiB Asignados
|
||||
settings.memory.allocate.manual.exceeded=%1$.1f GiB Asignados (%3$.1f GiB Disponibles)
|
||||
settings.memory.auto_allocate=Asignar automáticamente
|
||||
settings.memory.lower_bound=Memoria mínima
|
||||
settings.memory.unit.mib=MiB
|
||||
settings.memory.used_per_total=%1$.1f GiB Utilizados / %2$.1f GiB Totales
|
||||
settings.physical_memory=Tamaño de la memoria física
|
||||
settings.show_log=Mostrar registros
|
||||
|
||||
@@ -289,6 +289,7 @@ download=ダウンロード
|
||||
download.hint=ゲームや modpack をインストールするか、mod、リソース パック、マップをダウンロードします
|
||||
download.code.404=リモートサーバーにファイルが見つかりません:%s
|
||||
download.content=ゲームコンテンツ
|
||||
download.shader=シェーダー
|
||||
download.existing=ファイルは既に存在するため、保存できません。「名前を付けて保存」を選択して、ファイルを別の場所に保存できます。
|
||||
download.external_link=ダウンロードサイトを開く
|
||||
download.failed=%1$s のダウンロードに失敗しました、応答コード:%2$d
|
||||
@@ -700,7 +701,6 @@ datapack.title=World %s -データパック
|
||||
world=マップ
|
||||
world.add=マップを追加(.zip)
|
||||
world.datapack=データパックの管理
|
||||
world.datapack.1_13=Minecraft1.13以降のバージョンのみがデータパックをサポートします。
|
||||
world.datetime=最終ゲーム時刻:%s
|
||||
world.download=ダウンロード
|
||||
world.export=このマップをエクスポートする
|
||||
@@ -713,7 +713,8 @@ world.import.already_exists=このマップはすでに存在しています。
|
||||
world.import.choose=インポートするzipファイルを選択してください
|
||||
world.import.failed=このマップをインポートできません:%s
|
||||
world.import.invalid=無効なワールドzipファイル
|
||||
world.manage=マップ / データパック
|
||||
world.manage=マップ
|
||||
world.manage.title=マップ - %s
|
||||
world.name=マップ名
|
||||
world.name.enter=マップ名を入力してください
|
||||
world.show_all=すべて表示
|
||||
@@ -867,13 +868,14 @@ settings.launcher.title_transparent=タイトルの透明性
|
||||
settings.launcher.version_list_source=バージョンリストソース
|
||||
|
||||
settings.memory=ゲームメモリ
|
||||
settings.memory.allocate.auto=最小 %1$.1f GB /実際の %2$.1f GB
|
||||
settings.memory.allocate.auto.exceeded=最小 %1$.1f GB /実際の %2$.1f GB(%3$.1f GB使用可能)
|
||||
settings.memory.allocate.manual=割り当て %1$.1f GB
|
||||
settings.memory.allocate.manual.exceeded=割り当て %1$.1f GB(%3$.1f GB使用可能)
|
||||
settings.memory.allocate.auto=最小 %1$.1f GiB /実際の %2$.1f GiB
|
||||
settings.memory.allocate.auto.exceeded=最小 %1$.1f GiB /実際の %2$.1f GiB(%3$.1f GiB使用可能)
|
||||
settings.memory.allocate.manual=割り当て %1$.1f GiB
|
||||
settings.memory.allocate.manual.exceeded=割り当て %1$.1f GiB(%3$.1f GiB使用可能)
|
||||
settings.memory.auto_allocate=メモリサイズの自動割り当て
|
||||
settings.memory.lower_bound=最小メモリ
|
||||
settings.memory.used_per_total=%1$.1f GB使用済み/%2$.1fGB合計
|
||||
settings.memory.unit.mib=MiB
|
||||
settings.memory.used_per_total=%1$.1f GiB使用済み/%2$.1fGiB合計
|
||||
settings.physical_memory=物理メモリサイズ
|
||||
settings.show_log=ログを表示
|
||||
settings.tabs.installers=Installers
|
||||
|
||||
@@ -334,6 +334,7 @@ download=Скачать
|
||||
download.hint=Установить игры и модпаки или скачать моды, пакеты ресурсов и миры.
|
||||
download.code.404=Файл «%s» не найден на удаленном сервере.
|
||||
download.content=Аддоны
|
||||
download.shader=Шейдеры
|
||||
download.curseforge.unavailable=Эта сборка HMCL не поддерживает доступ к CurseForge. Пожалуйста, используйте официальную сборку для доступа к CurseForge.
|
||||
download.existing=Файл существует и по этому не может быть сохранён. Можно использовать «Сохранить как», чтобы сохранить файл в другом месте.
|
||||
download.external_link=Открыть сайт
|
||||
@@ -402,6 +403,11 @@ fatal.deprecated_java_version=В будущем для работы HMCL пот
|
||||
Рекомендуется установить последнюю версию Java, чтобы обеспечить правильную работу HMCL.\n\
|
||||
\n\
|
||||
Вы можете продолжать использовать старую версию Java. HMCL может распознавать и управлять несколькими версиями Java и автоматически выбирать подходящую Java в зависимости от версии игры.
|
||||
fatal.deprecated_java_version.update=В будущем для работы HMCL потребуется Java 11 или более поздняя версия. Пожалуйста, установить последнюю версию Java, чтобы убедиться, что приложение сможет завершить обновление.\n\
|
||||
\n\
|
||||
Вы можете продолжать использовать старую версию Java.\
|
||||
HMCL может распознавать и управлять несколькими версиями Java \
|
||||
и автоматически выбирать подходящую Java в зависимости от версии игры.
|
||||
fatal.deprecated_java_version.download_link=Скачать Java %d
|
||||
fatal.samba=Если вы пытаетесь открыть лаунчер в общей папке Samba, он может не работать, попробуйте обновить Java или запустить лаунчер из локальной папки.
|
||||
fatal.illegal_char=Недопустимый символ «=» в пути к папке пользователя. Лаунчер может работать, но некоторые функции не будут работать.\n\
|
||||
@@ -436,7 +442,9 @@ folder.mod=Моды
|
||||
folder.resourcepacks=Пакеты ресурсов
|
||||
folder.shaderpacks=Пакеты шейдеров
|
||||
folder.saves=Миры
|
||||
folder.schematics=Схемы
|
||||
folder.screenshots=Снимки экрана
|
||||
folder.world=Папка мира
|
||||
|
||||
game=Игры
|
||||
game.crash.feedback=<b>Пожалуйста, не делитесь скриншотами этого интерфейса с другими!</b> Если вы просите помощи у других, нажмите <b>«Экспорт журналов с ошибками»</b> и отправьте экспортированный файл другим для анализа.
|
||||
@@ -534,7 +542,7 @@ game.crash.reason.jvm_32bit=Игра отказала из-за того, что
|
||||
\n\
|
||||
Если ваша ОС 64-разрядная, установите и используйте 64-разрядная Java. Если ваша ОС 32-разрядная, вам может потребоваться переустановить 64-разрядную ОС или приобрести более современный компьютер.\n\
|
||||
\n\
|
||||
Или вы можете отключить опцию «Автовыделение» в разделе «Глобальные настройки / Раздельные настройки для сборки → Оперативная память» и указать максимальный размер выделяемой памяти 1024 МБ или меньше.
|
||||
Или вы можете отключить опцию «Автовыделение» в разделе «Глобальные настройки / Раздельные настройки для сборки → Оперативная память» и указать максимальный размер выделяемой памяти 1024 МиБ или меньше.
|
||||
game.crash.reason.loading_crashed_forge=Игра отказала из-за мода «%1$s» (%2$s).\n\
|
||||
\n\
|
||||
Вы можете попробовать удалить или обновить его.
|
||||
@@ -776,9 +784,9 @@ launch.advice.java8_51_1_13=Minecraft 1.13 может аварийно заве
|
||||
launch.advice.java9=Вы не сможете запустить Minecraft 1.12 и ниже с Java 9 и более новыми версиями Java. Используйте вместо этого Java 8.
|
||||
launch.advice.modded_java=Некоторые моды могут быть несовместимы с новыми версиями Java. Рекомендуется использовать Java %s для запуска Minecraft %s.
|
||||
launch.advice.newer_java=Вы используете старую версию Java для запуска игры. Рекомендуется обновить Java 8, иначе некоторые моды могут привести к сбою игры.
|
||||
launch.advice.not_enough_space=Вы выделили слишком много памяти, поскольку размер физической памяти составляет %d МБ, ваша игра может рухнуть.
|
||||
launch.advice.not_enough_space=Вы выделили слишком много памяти, поскольку размер физической памяти составляет %d МиБ, ваша игра может рухнуть.
|
||||
launch.advice.require_newer_java_version=Для текущей версии игры требуется Java %s, но мы не смогли ее найти. Хотите скачать ее сейчас?
|
||||
launch.advice.too_large_memory_for_32bit=Вы выделили слишком много памяти, из-за 32-разрядной JRE ваша игра может рухнуть. Максимальный объем памяти для 32-разрядных систем составляет 1024 МБ.
|
||||
launch.advice.too_large_memory_for_32bit=Вы выделили слишком много памяти, из-за 32-разрядной JRE ваша игра может рухнуть. Максимальный объем памяти для 32-разрядных систем составляет 1024 МиБ.
|
||||
launch.advice.vanilla_linux_java_8=На Linux x86-64, Minecraft 1.12.2 и ниже может работать только на Java 8.\nВерсии Java 9 и выше не могут загружать 32-битные нативные библиотеки, такие как liblwjgl.so.
|
||||
launch.advice.modlauncher8=Используемая вами версия Forge несовместима с текущей версией Java. Попробуйте обновить Forge.
|
||||
launch.advice.vanilla_x86.translation=Minecraft еще не полностью поддерживается на вашей платформе, поэтому вы можете столкнуться с отсутствием функций или даже не сможете запустить игру.\n\
|
||||
@@ -935,11 +943,16 @@ modpack.wizard.step.initialization.warning=Перед созданием мод
|
||||
modpack.wizard.step.initialization.server=Нажмите здесь для получения дополнительных руководств по созданию модпака на сервере, который может автообновляться.
|
||||
|
||||
modrinth.category.adventure=Приключение
|
||||
modrinth.category.atmosphere=Атмосфера
|
||||
modrinth.category.audio=Аудио
|
||||
modrinth.category.blocks=Блоки
|
||||
modrinth.category.bloom=Свечение
|
||||
modrinth.category.bukkit=Bukkit
|
||||
modrinth.category.bungeecord=BungeeCord
|
||||
modrinth.category.canvas=Canvas
|
||||
modrinth.category.cartoon=Мультяшный
|
||||
modrinth.category.challenging=Вызов
|
||||
modrinth.category.colored-lighting=Цветное освещение
|
||||
modrinth.category.core-shaders=Ядро шейдеров
|
||||
modrinth.category.combat=Бой
|
||||
modrinth.category.cursed=Cursed
|
||||
@@ -949,19 +962,25 @@ modrinth.category.entities=Сущности
|
||||
modrinth.category.environment=Окружающая среда
|
||||
modrinth.category.equipment=Оборудование
|
||||
modrinth.category.fabric=Fabric
|
||||
modrinth.category.fantasy=Фэнтези
|
||||
modrinth.category.foliage=Растительность
|
||||
modrinth.category.fonts=Шрифты
|
||||
modrinth.category.food=Еда
|
||||
modrinth.category.forge=Forge
|
||||
modrinth.category.game-mechanics=Игровая механика
|
||||
modrinth.category.gui=ГИП
|
||||
modrinth.category.high=Высокий
|
||||
modrinth.category.iris=Iris
|
||||
modrinth.category.items=Предметы
|
||||
modrinth.category.kitchen-sink=Kitchen-Sink
|
||||
modrinth.category.library=Библиотека
|
||||
modrinth.category.lightweight=Легкий
|
||||
modrinth.category.liteloader=LiteLoader
|
||||
modrinth.category.locale=Язык
|
||||
modrinth.category.low=Низкий
|
||||
modrinth.category.magic=Магия
|
||||
modrinth.category.management=Управление
|
||||
modrinth.category.medium=Средний
|
||||
modrinth.category.minecraft=Minecraft
|
||||
modrinth.category.minigame=Мини-игра
|
||||
modrinth.category.misc=Разное
|
||||
@@ -971,13 +990,21 @@ modrinth.category.models=Модели
|
||||
modrinth.category.modloader=Мод-загрузчик
|
||||
modrinth.category.multiplayer=Сетевая игра
|
||||
modrinth.category.neoforge=NeoForge
|
||||
modrinth.category.optifine=OptiFine
|
||||
modrinth.category.optimization=Оптимизация
|
||||
modrinth.category.paper=Paper
|
||||
modrinth.category.path-tracing=Трассировка пути
|
||||
modrinth.category.pbr=PBR
|
||||
modrinth.category.potato=Минимум
|
||||
modrinth.category.purpur=Purpur
|
||||
modrinth.category.quests=Квесты
|
||||
modrinth.category.quilt=Quilt
|
||||
modrinth.category.realistic=Реалистичный
|
||||
modrinth.category.reflections=Отражения
|
||||
modrinth.category.rift=Rift
|
||||
modrinth.category.screenshot=Экстрим
|
||||
modrinth.category.semi-realistic=Полуреалистичный
|
||||
modrinth.category.shadows=Тени
|
||||
modrinth.category.simplistic=Упрощенный
|
||||
modrinth.category.social=Социальная
|
||||
modrinth.category.spigot=Spigot
|
||||
@@ -988,6 +1015,7 @@ modrinth.category.themed=Тематический
|
||||
modrinth.category.transportation=Транспорт
|
||||
modrinth.category.tweaks=Усовершенствование
|
||||
modrinth.category.utility=Утилиты
|
||||
modrinth.category.vanilla=Ванильное
|
||||
modrinth.category.vanilla-like=Ванильное
|
||||
modrinth.category.velocity=Velocity
|
||||
modrinth.category.waterfall=Waterfall
|
||||
@@ -1075,9 +1103,12 @@ world.backup.create.locked=В настоящее время мир находи
|
||||
world.backup.create.success=Успешно создано новое резервное копирование: %s
|
||||
world.backup.delete=Удалить эту резервную копию
|
||||
world.backup.processing=Создание новой резервной копии ...
|
||||
world.backup.title=Мир [%s] - Резервные копии
|
||||
world.datapack=Управлять наборами данных
|
||||
world.datapack.1_13=Только Minecraft 1.13 или новее поддерживает наборы данных.
|
||||
world.chunkbase=Chunk Base
|
||||
world.chunkbase.end_city=Город Края
|
||||
world.chunkbase.seed_map=Предпросмотр генерации мира
|
||||
world.chunkbase.stronghold=Крепость
|
||||
world.chunkbase.nether_fortress=Крепость Нижнего мира
|
||||
world.datapack=Наборы данных
|
||||
world.datetime=Последний запуск игры %s
|
||||
world.download=Скачать мир
|
||||
world.download.title=Скачать мир - %1s
|
||||
@@ -1092,7 +1123,6 @@ world.import.choose=Выберите архив мира, который хот
|
||||
world.import.failed=Не удалось импортировать этот мир\: %s
|
||||
world.import.invalid=Не удалось разобрать мир.
|
||||
world.info=Сведения о мире
|
||||
world.info.title=Мир [%s] - Сведения
|
||||
world.info.basic=Основные сведения
|
||||
world.info.allow_cheats=Разрешить команды/читы
|
||||
world.info.dimension.the_nether=Нижний мир
|
||||
@@ -1123,6 +1153,8 @@ world.info.time=Время игры
|
||||
world.info.time.format=%s дн.
|
||||
world.locked=В эксплуатации
|
||||
world.manage=Миры
|
||||
world.manage.button=Управлять
|
||||
world.manage.title=Мир - %s
|
||||
world.name=Название мира
|
||||
world.name.enter=Введите название мира
|
||||
world.show_all=Показать все
|
||||
@@ -1218,7 +1250,7 @@ settings.advanced.environment_variables=Переменные среды
|
||||
settings.advanced.game_dir.default=По умолчанию («.minecraft/»)
|
||||
settings.advanced.game_dir.independent=Раздельно («.minecraft/versions/<имя сборки>/», кроме assets и библиотек)
|
||||
settings.advanced.java_permanent_generation_space=Пространство PermGen
|
||||
settings.advanced.java_permanent_generation_space.prompt=в МБ
|
||||
settings.advanced.java_permanent_generation_space.prompt=в МиБ
|
||||
settings.advanced.jvm=Настройки Java VM
|
||||
settings.advanced.jvm_args=Аргументы Java VM
|
||||
settings.advanced.jvm_args.prompt=\ · Если параметр, введённый в «Аргументы Java VM», совпадает с параметром по умолчанию, он не будет добавлен.\n\
|
||||
@@ -1328,13 +1360,14 @@ settings.launcher.turn_off_animations=Отключить анимацию (Пр
|
||||
settings.launcher.version_list_source=Список версий
|
||||
|
||||
settings.memory=Оперативная память
|
||||
settings.memory.allocate.auto=Минимум %1$.1f ГБ / Выделено %2$.1f ГБ
|
||||
settings.memory.allocate.auto.exceeded=Минимум %1$.1f ГБ / Выделено %2$.1f ГБ (Доступно %3$.1f ГБ)
|
||||
settings.memory.allocate.manual=Выделено %1$.1f ГБ
|
||||
settings.memory.allocate.manual.exceeded=Выделено %1$.1f ГБ (Доступно %3$.1f ГБ)
|
||||
settings.memory.allocate.auto=Минимум %1$.1f ГиБ / Выделено %2$.1f ГиБ
|
||||
settings.memory.allocate.auto.exceeded=Минимум %1$.1f ГиБ / Выделено %2$.1f ГиБ (Доступно %3$.1f ГиБ)
|
||||
settings.memory.allocate.manual=Выделено %1$.1f ГиБ
|
||||
settings.memory.allocate.manual.exceeded=Выделено %1$.1f ГиБ (Доступно %3$.1f ГиБ)
|
||||
settings.memory.auto_allocate=Автовыделение
|
||||
settings.memory.lower_bound=Мин. объём памяти
|
||||
settings.memory.used_per_total=Использовано %1$.1f ГБ / Всего %2$.1f ГБ
|
||||
settings.memory.unit.mib=МиБ
|
||||
settings.memory.used_per_total=Использовано %1$.1f ГиБ / Всего %2$.1f ГиБ
|
||||
settings.physical_memory=Размер физической памяти
|
||||
settings.show_log=Показать логи
|
||||
settings.tabs.installers=Погрузчики
|
||||
|
||||
@@ -334,6 +334,7 @@ download=下載
|
||||
download.hint=安裝遊戲和模組包或下載模組、資源包和地圖
|
||||
download.code.404=遠端伺服器沒有需要下載的檔案:%s
|
||||
download.content=遊戲內容
|
||||
download.shader=光影
|
||||
download.curseforge.unavailable=這個 HMCL 版本不支援訪問 CurseForge。請使用官方版本進行下載。
|
||||
download.existing=檔案已存在,無法儲存。你可以將檔案儲存至其他地方。
|
||||
download.external_link=開啟下載網站
|
||||
@@ -380,6 +381,7 @@ fatal.migration_requires_manual_reboot=Hello Minecraft! Launcher 即將升級完
|
||||
fatal.apply_update_failure=我們很抱歉 Hello Minecraft! Launcher 無法自動完成升級程式,因為出現了一些問題。\n但你依然可以從 %s 處手動下載 HMCL 來完成升級。
|
||||
fatal.apply_update_need_win7=Hello Minecraft! Launcher 無法在 Windows XP/Vista 上進行自動更新。請從 %s 處手動下載 HMCL 來完成升級。
|
||||
fatal.deprecated_java_version=HMCL 未來需要 Java 11 或更高版本才能執行,但依然支援使用 Java 8 啟動遊戲。建議安裝最新版本的 Java 以確保 HMCL 正常執行。\n你可以繼續保留舊版本 Java。HMCL 能夠識別與管理多個 Java,並會自動根據遊戲版本為你選取合適的 Java。
|
||||
fatal.deprecated_java_version.update=更高版本的 HMCL 需要 Java 11 或更高版本才能執行,請安裝最新版本的 Java 以確保 HMCL 能夠完成升級。\n你可以繼續保留舊版本 Java。HMCL 能夠識別與管理多個 Java,並會自動根據遊戲版本為你選取合適的 Java。
|
||||
fatal.deprecated_java_version.download_link=下載 Java %d
|
||||
fatal.samba=如果您正在透過 Samba 共亯的目錄中開啟 Hello Minecraft! Launcher,啟動器可能無法正常工作,請嘗試更新您的 Java 或在本機目錄內開啟 HMCL。
|
||||
fatal.illegal_char=由於您的使用者目錄路徑中存在無效字元『=』,您將無法使用外部登入帳戶以及離線登入更換外觀功能。
|
||||
@@ -408,6 +410,7 @@ folder.shaderpacks=著色器包目錄
|
||||
folder.saves=遊戲存檔目錄
|
||||
folder.schematics=原理圖目錄
|
||||
folder.screenshots=截圖目錄
|
||||
folder.world=世界目錄
|
||||
|
||||
game=遊戲
|
||||
game.crash.feedback=<b>請不要將本介面截圖給他人!</b>如果你要求助他人,請你點擊左下角「匯出遊戲崩潰資訊」後將匯出的檔案發送給他人以供分析。\n你可以點擊下方的「幫助」前往社群尋求幫助。
|
||||
@@ -742,11 +745,16 @@ modpack.wizard.step.initialization.warning=在製作模組包前,請你確認
|
||||
modpack.wizard.step.initialization.server=點選此處查看有關伺服器自動更新模組包的製作教學
|
||||
|
||||
modrinth.category.adventure=冒險
|
||||
modrinth.category.atmosphere=氛圍
|
||||
modrinth.category.audio=聲音
|
||||
modrinth.category.blocks=方塊
|
||||
modrinth.category.bloom=泛光
|
||||
modrinth.category.bukkit=Bukkit
|
||||
modrinth.category.bungeecord=BungeeCord
|
||||
modrinth.category.canvas=Canvas
|
||||
modrinth.category.cartoon=卡通
|
||||
modrinth.category.challenging=高難度
|
||||
modrinth.category.colored-lighting=彩色光照
|
||||
modrinth.category.core-shaders=核心著色器
|
||||
modrinth.category.combat=戰鬥
|
||||
modrinth.category.cursed=Cursed
|
||||
@@ -756,19 +764,25 @@ modrinth.category.entities=實體
|
||||
modrinth.category.environment=環境
|
||||
modrinth.category.equipment=裝備
|
||||
modrinth.category.fabric=Fabric
|
||||
modrinth.category.fantasy=幻想
|
||||
modrinth.category.foliage=植被
|
||||
modrinth.category.fonts=字體
|
||||
modrinth.category.food=食物
|
||||
modrinth.category.forge=Forge
|
||||
modrinth.category.game-mechanics=遊戲機制
|
||||
modrinth.category.gui=GUI
|
||||
modrinth.category.high=高
|
||||
modrinth.category.iris=Iris
|
||||
modrinth.category.items=物品
|
||||
modrinth.category.kitchen-sink=大雜燴
|
||||
modrinth.category.library=支援庫
|
||||
modrinth.category.lightweight=輕量
|
||||
modrinth.category.liteloader=LiteLoader
|
||||
modrinth.category.locale=在地化
|
||||
modrinth.category.low=低
|
||||
modrinth.category.magic=魔法
|
||||
modrinth.category.management=管理
|
||||
modrinth.category.medium=中
|
||||
modrinth.category.minecraft=Minecraft
|
||||
modrinth.category.minigame=小遊戲
|
||||
modrinth.category.misc=其他
|
||||
@@ -778,13 +792,21 @@ modrinth.category.models=模型
|
||||
modrinth.category.modloader=ModLoader
|
||||
modrinth.category.multiplayer=多人
|
||||
modrinth.category.neoforge=NeoForge
|
||||
modrinth.category.optifine=OptiFine
|
||||
modrinth.category.optimization=最佳化
|
||||
modrinth.category.paper=Paper
|
||||
modrinth.category.path-tracing=路径追踪
|
||||
modrinth.category.pbr=PBR
|
||||
modrinth.category.potato=極低
|
||||
modrinth.category.purpur=Purpur
|
||||
modrinth.category.quests=任務
|
||||
modrinth.category.quilt=Quilt
|
||||
modrinth.category.realistic=寫實
|
||||
modrinth.category.reflections=反射
|
||||
modrinth.category.rift=Rift
|
||||
modrinth.category.screenshot=極高
|
||||
modrinth.category.semi-realistic=半寫實
|
||||
modrinth.category.shadows=陰影
|
||||
modrinth.category.simplistic=簡單
|
||||
modrinth.category.social=社交
|
||||
modrinth.category.spigot=Spigot
|
||||
@@ -795,6 +817,7 @@ modrinth.category.themed=主題
|
||||
modrinth.category.transportation=運輸
|
||||
modrinth.category.tweaks=最佳化
|
||||
modrinth.category.utility=實用
|
||||
modrinth.category.vanilla=原生
|
||||
modrinth.category.vanilla-like=類原生
|
||||
modrinth.category.velocity=Velocity
|
||||
modrinth.category.waterfall=Waterfall
|
||||
@@ -863,7 +886,7 @@ datapack.title=世界 [%s] - 資料包
|
||||
|
||||
web.failed=加載頁面失敗
|
||||
web.open_in_browser=是否要在瀏覽器中開啟此連結:\n%s
|
||||
web.view_in_browser=在瀏覽器中查看
|
||||
web.view_in_browser=在瀏覽器中查看完整日誌
|
||||
|
||||
world=世界
|
||||
world.add=新增世界
|
||||
@@ -874,9 +897,12 @@ world.backup.create.locked=該世界正在使用中,請關閉遊戲後重試
|
||||
world.backup.create.success=成功創建新備份:%s
|
||||
world.backup.delete=删除此備份
|
||||
world.backup.processing=正在備份中……
|
||||
world.backup.title=世界 [%s] - 備份
|
||||
world.datapack=管理資料包
|
||||
world.datapack.1_13=僅 Minecraft 1.13 及之後的版本支援資料包
|
||||
world.chunkbase=世界地圖
|
||||
world.chunkbase.end_city=終界城地圖
|
||||
world.chunkbase.seed_map=種子地圖
|
||||
world.chunkbase.stronghold=要塞地圖
|
||||
world.chunkbase.nether_fortress=地獄要塞地圖
|
||||
world.datapack=資料包管理
|
||||
world.datetime=上一次遊戲時間: %s
|
||||
world.download=下載世界
|
||||
world.download.title=世界下載 - %1s
|
||||
@@ -890,7 +916,6 @@ world.import.choose=選取要匯入的存檔壓縮檔
|
||||
world.import.failed=無法匯入此世界: %s
|
||||
world.import.invalid=無法識別的存檔壓縮檔
|
||||
world.info=世界資訊
|
||||
world.info.title=世界 [%s] - 世界資訊
|
||||
world.info.basic=基本資訊
|
||||
world.info.allow_cheats=允許指令(作弊)
|
||||
world.info.dimension.the_nether=地獄
|
||||
@@ -921,7 +946,9 @@ world.info.time=遊戲內時間
|
||||
world.info.time.format=%s 天
|
||||
world.locked=使用中
|
||||
world.game_version=遊戲版本
|
||||
world.manage=世界/資料包
|
||||
world.manage=世界管理
|
||||
world.manage.button=世界管理
|
||||
world.manage.title=世界管理 - %s
|
||||
world.name=世界名稱
|
||||
world.name.enter=輸入世界名稱
|
||||
world.show_all=全部顯示
|
||||
@@ -1124,6 +1151,7 @@ settings.memory.allocate.manual=遊戲分配 %1$.1f GiB
|
||||
settings.memory.allocate.manual.exceeded=遊戲分配 %1$.1f GiB (%3$.1f GiB 可用)
|
||||
settings.memory.auto_allocate=自動分配
|
||||
settings.memory.lower_bound=最低分配
|
||||
settings.memory.unit.mib=MiB
|
||||
settings.memory.used_per_total=已使用 %1$.1f GiB / 總記憶體 %2$.1f GiB
|
||||
settings.physical_memory=實體記憶體大小
|
||||
settings.show_log=查看日誌
|
||||
|
||||
@@ -342,6 +342,7 @@ download=下载
|
||||
download.hint=安装游戏和整合包或下载模组、资源包和地图
|
||||
download.code.404=远程服务器不包含需要下载的文件: %s\n你可以点击右上角帮助按钮进行求助。
|
||||
download.content=游戏内容
|
||||
download.shader=光影
|
||||
download.curseforge.unavailable=此 HMCL 版本不支持访问 CurseForge。请使用官方版本进行下载。
|
||||
download.existing=文件已存在,无法保存。你可以将文件保存至其他地方。
|
||||
download.external_link=打开下载网站
|
||||
@@ -389,6 +390,7 @@ fatal.migration_requires_manual_reboot=Hello Minecraft! Launcher 即将完成升
|
||||
fatal.apply_update_failure=我们很抱歉 Hello Minecraft! Launcher 无法自动完成升级,因为出现了一些问题。\n但你依可以从 %s 手动下载 HMCL 来完成升级。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。
|
||||
fatal.apply_update_need_win7=Hello Minecraft! Launcher 无法在 Windows XP/Vista 上进行自动更新。请从 %s 手动下载 HMCL 来完成升级。
|
||||
fatal.deprecated_java_version=HMCL 未来需要 Java 11 或更高版本才能运行,但依然支持使用 Java 8 启动游戏。建议安装最新版本的 Java 以确保 HMCL 能正常工作。\n你可以继续保留旧版本 Java。HMCL 能够识别与管理多个 Java,并会自动根据游戏版本为你选择合适的 Java。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。
|
||||
fatal.deprecated_java_version.update=更高版本的 HMCL 需要 Java 11 或更高版本才能运行,请安装最新版本的 Java 以确保 HMCL 能够完成升级。\n你可以继续保留旧版本 Java。HMCL 能够识别与管理多个 Java,并会自动根据游戏版本为你选择合适的 Java。
|
||||
fatal.deprecated_java_version.download_link=下载 Java %d
|
||||
fatal.samba=如果你正在通过 Samba 共享的文件夹中运行 Hello Minecraft! Launcher,启动器可能无法正常工作。请尝试更新你的 Java 或在本地文件夹内运行 HMCL。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。
|
||||
fatal.illegal_char=由于你的用户文件夹路径中存在非法字符“=”,你将无法使用外置登录账户以及离线登录更换皮肤功能。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。
|
||||
@@ -417,6 +419,7 @@ folder.shaderpacks=光影包文件夹
|
||||
folder.saves=存档文件夹
|
||||
folder.schematics=原理图文件夹
|
||||
folder.screenshots=截图文件夹
|
||||
folder.world=世界文件夹
|
||||
|
||||
game=游戏
|
||||
game.crash.feedback=<b>请不要将本界面截图给他人!</b>如果你要向他人求助,请你点击左下角<b>“导出游戏崩溃信息”</b>后将导出的文件发送给他人以供分析。\n你可以点击下方的<b>“帮助”</b>前往交流群寻求帮助。
|
||||
@@ -752,11 +755,16 @@ modpack.wizard.step.initialization.warning=在制作整合包前,请你确认
|
||||
modpack.wizard.step.initialization.server=点击此处查看有关服务器自动更新整合包的制作教程
|
||||
|
||||
modrinth.category.adventure=冒险
|
||||
modrinth.category.atmosphere=氛围
|
||||
modrinth.category.audio=声音
|
||||
modrinth.category.blocks=方块
|
||||
modrinth.category.bloom=泛光
|
||||
modrinth.category.bukkit=Bukkit
|
||||
modrinth.category.bungeecord=BungeeCord
|
||||
modrinth.category.canvas=Canvas
|
||||
modrinth.category.cartoon=卡通
|
||||
modrinth.category.challenging=高难度
|
||||
modrinth.category.colored-lighting=彩色光照
|
||||
modrinth.category.core-shaders=核心着色器
|
||||
modrinth.category.combat=战斗
|
||||
modrinth.category.cursed=Cursed
|
||||
@@ -766,19 +774,25 @@ modrinth.category.entities=实体
|
||||
modrinth.category.environment=环境
|
||||
modrinth.category.equipment=装备
|
||||
modrinth.category.fabric=Fabric
|
||||
modrinth.category.fantasy=幻想
|
||||
modrinth.category.foliage=植被
|
||||
modrinth.category.fonts=字体
|
||||
modrinth.category.food=食物
|
||||
modrinth.category.forge=Forge
|
||||
modrinth.category.game-mechanics=游戏机制
|
||||
modrinth.category.gui=GUI
|
||||
modrinth.category.high=高
|
||||
modrinth.category.iris=Iris
|
||||
modrinth.category.items=物品
|
||||
modrinth.category.kitchen-sink=大杂烩
|
||||
modrinth.category.library=支持库
|
||||
modrinth.category.lightweight=轻量
|
||||
modrinth.category.liteloader=LiteLoader
|
||||
modrinth.category.locale=本地化
|
||||
modrinth.category.low=低
|
||||
modrinth.category.magic=魔法
|
||||
modrinth.category.management=管理
|
||||
modrinth.category.medium=中
|
||||
modrinth.category.minecraft=Minecraft
|
||||
modrinth.category.minigame=小游戏
|
||||
modrinth.category.misc=其他
|
||||
@@ -788,13 +802,21 @@ modrinth.category.models=模型
|
||||
modrinth.category.modloader=Modloader
|
||||
modrinth.category.multiplayer=多人
|
||||
modrinth.category.neoforge=NeoForge
|
||||
modrinth.category.optifine=OptiFine
|
||||
modrinth.category.optimization=优化
|
||||
modrinth.category.paper=Paper
|
||||
modrinth.category.path-tracing=路径追踪
|
||||
modrinth.category.pbr=PBR
|
||||
modrinth.category.potato=极低
|
||||
modrinth.category.purpur=Purpur
|
||||
modrinth.category.quests=任务
|
||||
modrinth.category.quilt=Quilt
|
||||
modrinth.category.realistic=写实
|
||||
modrinth.category.reflections=反射
|
||||
modrinth.category.rift=Rift
|
||||
modrinth.category.screenshot=极高
|
||||
modrinth.category.semi-realistic=半写实
|
||||
modrinth.category.shadows=阴影
|
||||
modrinth.category.simplistic=简单
|
||||
modrinth.category.social=社交
|
||||
modrinth.category.spigot=Spigot
|
||||
@@ -805,6 +827,7 @@ modrinth.category.themed=主题
|
||||
modrinth.category.transportation=运输
|
||||
modrinth.category.tweaks=优化
|
||||
modrinth.category.utility=实用
|
||||
modrinth.category.vanilla=原生
|
||||
modrinth.category.vanilla-like=类原生
|
||||
modrinth.category.velocity=Velocity
|
||||
modrinth.category.waterfall=Waterfall
|
||||
@@ -873,20 +896,23 @@ datapack.title=世界 [%s] - 数据包
|
||||
|
||||
web.failed=加载页面失败
|
||||
web.open_in_browser=是否要在浏览器中打开此链接:\n%s
|
||||
web.view_in_browser=在浏览器中查看
|
||||
web.view_in_browser=在浏览器中查看完整日志
|
||||
|
||||
world=世界
|
||||
world.add=添加世界
|
||||
world.backup=世界备份
|
||||
world.backup=备份管理
|
||||
world.backup.create.new_one=创建新备份
|
||||
world.backup.create.failed=创建备份失败。\n%s
|
||||
world.backup.create.locked=该世界正在使用中,请关闭游戏后重试。
|
||||
world.backup.create.success=成功创建新备份:%s
|
||||
world.backup.delete=删除此备份
|
||||
world.backup.processing=正在备份中……
|
||||
world.backup.title=世界 [%s] - 备份
|
||||
world.datapack=管理数据包
|
||||
world.datapack.1_13=仅 Minecraft 1.13 及之后的版本支持数据包
|
||||
world.chunkbase=世界地图
|
||||
world.chunkbase.end_city=末地城地图
|
||||
world.chunkbase.seed_map=种子地图
|
||||
world.chunkbase.stronghold=要塞地图
|
||||
world.chunkbase.nether_fortress=下界要塞地图
|
||||
world.datapack=数据包管理
|
||||
world.datetime=上一次游戏时间: %s
|
||||
world.download=下载世界
|
||||
world.download.title=世界下载 - %1s
|
||||
@@ -901,7 +927,6 @@ world.import.choose=选择要导入的存档压缩包
|
||||
world.import.failed=无法导入此世界:%s
|
||||
world.import.invalid=无法识别该存档压缩包
|
||||
world.info=世界信息
|
||||
world.info.title=世界 [%s] - 世界信息
|
||||
world.info.basic=基本信息
|
||||
world.info.allow_cheats=允许命令(作弊)
|
||||
world.info.dimension.the_nether=下界
|
||||
@@ -931,7 +956,9 @@ world.info.random_seed=种子
|
||||
world.info.time=游戏内时间
|
||||
world.info.time.format=%s 天
|
||||
world.locked=使用中
|
||||
world.manage=世界/数据包
|
||||
world.manage=世界管理
|
||||
world.manage.button=世界管理
|
||||
world.manage.title=世界管理 - %s
|
||||
world.name=世界名称
|
||||
world.name.enter=输入世界名称
|
||||
world.show_all=显示全部
|
||||
@@ -1134,6 +1161,7 @@ settings.memory.allocate.manual=游戏分配 %1$.1f GiB
|
||||
settings.memory.allocate.manual.exceeded=游戏分配 %1$.1f GiB (设备仅 %3$.1f GiB 可用)
|
||||
settings.memory.auto_allocate=自动分配内存
|
||||
settings.memory.lower_bound=最低内存分配
|
||||
settings.memory.unit.mib=MiB
|
||||
settings.memory.used_per_total=设备中已使用 %1$.1f GiB / 设备总内存 %2$.1f GiB
|
||||
settings.physical_memory=物理内存大小
|
||||
settings.show_log=查看日志
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+2
@@ -41,6 +41,8 @@ public enum DiscoJavaDistribution implements JavaDistribution<DiscoJavaRemoteVer
|
||||
pair(WINDOWS, EnumSet.of(X86_64, X86, ARM64)),
|
||||
pair(LINUX, EnumSet.of(X86_64, X86, ARM64, ARM32, RISCV64, PPC64, PPC64LE, S390X, SPARCV9)),
|
||||
pair(MACOS, EnumSet.of(X86_64, ARM64))),
|
||||
// Fucking javac generates an anonymous inner class here, whose constructor takes no @SafeVarargs annotation, causing a compile-time warning.
|
||||
@SuppressWarnings({"unchecked", "RedundantSuppression"})
|
||||
LIBERICA("BellSoft Liberica", "liberica", "BellSoft",
|
||||
EnumSet.of(JDK, JRE, JDKFX, JREFX),
|
||||
pair(WINDOWS, EnumSet.of(X86_64, X86, ARM64)),
|
||||
|
||||
@@ -209,7 +209,7 @@ public final class CrashReportAnalyzer {
|
||||
}
|
||||
}
|
||||
|
||||
public static Set<Result> anaylze(String log) {
|
||||
public static Set<Result> analyze(String log) {
|
||||
Set<Result> results = new HashSet<>();
|
||||
for (Rule rule : Rule.values()) {
|
||||
Matcher matcher = rule.pattern.matcher(log);
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.github.steveice10.opennbt.tag.builtin.StringTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
import javafx.scene.image.Image;
|
||||
import org.jackhuang.hmcl.util.io.*;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -31,6 +32,7 @@ import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.FileLock;
|
||||
import java.nio.channels.OverlappingFileLockException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.*;
|
||||
import java.util.List;
|
||||
@@ -41,7 +43,7 @@ import java.util.zip.GZIPOutputStream;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public class World {
|
||||
public final class World {
|
||||
|
||||
private final Path file;
|
||||
private String fileName;
|
||||
@@ -49,6 +51,8 @@ public class World {
|
||||
private String gameVersion;
|
||||
private long lastPlayed;
|
||||
private Image icon;
|
||||
private Long seed;
|
||||
private boolean largeBiomes;
|
||||
private boolean isLocked;
|
||||
|
||||
public World(Path file) throws IOException {
|
||||
@@ -65,7 +69,7 @@ public class World {
|
||||
private void loadFromDirectory() throws IOException {
|
||||
fileName = FileUtils.getName(file);
|
||||
Path levelDat = file.resolve("level.dat");
|
||||
getWorldName(levelDat);
|
||||
loadWorldInfo(levelDat);
|
||||
isLocked = isLocked(getSessionLockFile());
|
||||
|
||||
Path iconFile = file.resolve("icon.png");
|
||||
@@ -108,6 +112,14 @@ public class World {
|
||||
return gameVersion;
|
||||
}
|
||||
|
||||
public @Nullable Long getSeed() {
|
||||
return seed;
|
||||
}
|
||||
|
||||
public boolean isLargeBiomes() {
|
||||
return largeBiomes;
|
||||
}
|
||||
|
||||
public Image getIcon() {
|
||||
return icon;
|
||||
}
|
||||
@@ -121,7 +133,7 @@ public class World {
|
||||
if (!Files.exists(levelDat))
|
||||
throw new IOException("Not a valid world zip file since level.dat cannot be found.");
|
||||
|
||||
getWorldName(levelDat);
|
||||
loadWorldInfo(levelDat);
|
||||
|
||||
Path iconFile = root.resolve("icon.png");
|
||||
if (Files.isRegularFile(iconFile)) {
|
||||
@@ -154,7 +166,7 @@ public class World {
|
||||
}
|
||||
}
|
||||
|
||||
private void getWorldName(Path levelDat) throws IOException {
|
||||
private void loadWorldInfo(Path levelDat) throws IOException {
|
||||
CompoundTag nbt = parseLevelDat(levelDat);
|
||||
|
||||
CompoundTag data = nbt.get("Data");
|
||||
@@ -178,6 +190,27 @@ public class World {
|
||||
if (version.get("Name") instanceof StringTag)
|
||||
gameVersion = version.<StringTag>get("Name").getValue();
|
||||
}
|
||||
|
||||
Tag worldGenSettings = data.get("WorldGenSettings");
|
||||
if (worldGenSettings instanceof CompoundTag) {
|
||||
Tag seedTag = ((CompoundTag) worldGenSettings).get("seed");
|
||||
if (seedTag instanceof LongTag) {
|
||||
seed = ((LongTag) seedTag).getValue();
|
||||
}
|
||||
}
|
||||
if (seed == null) {
|
||||
Tag seedTag = data.get("RandomSeed");
|
||||
if (seedTag instanceof LongTag) {
|
||||
seed = ((LongTag) seedTag).getValue();
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Only work for 1.15 and below
|
||||
if (data.get("generatorName") instanceof StringTag) {
|
||||
largeBiomes = "largeBiomes".equals(data.<StringTag>get("generatorName").getValue());
|
||||
} else {
|
||||
largeBiomes = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void rename(String newName) throws IOException {
|
||||
@@ -293,7 +326,7 @@ public class World {
|
||||
private static boolean isLocked(Path sessionLockFile) {
|
||||
try (FileChannel fileChannel = FileChannel.open(sessionLockFile, StandardOpenOption.WRITE)) {
|
||||
return fileChannel.tryLock() == null;
|
||||
} catch (AccessDeniedException accessDeniedException) {
|
||||
} catch (AccessDeniedException | OverlappingFileLockException accessDeniedException) {
|
||||
return true;
|
||||
} catch (NoSuchFileException noSuchFileException) {
|
||||
return false;
|
||||
|
||||
+1
@@ -46,6 +46,7 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
public static final ModrinthRemoteModRepository MODS = new ModrinthRemoteModRepository("mod");
|
||||
public static final ModrinthRemoteModRepository MODPACKS = new ModrinthRemoteModRepository("modpack");
|
||||
public static final ModrinthRemoteModRepository RESOURCE_PACKS = new ModrinthRemoteModRepository("resourcepack");
|
||||
public static final ModrinthRemoteModRepository SHADER_PACKS = new ModrinthRemoteModRepository("shader");
|
||||
|
||||
private static final String PREFIX = "https://api.modrinth.com";
|
||||
|
||||
|
||||
@@ -45,8 +45,8 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -96,7 +96,10 @@ public final class MultiMCInstancePatch {
|
||||
@SerializedName("jarMods")
|
||||
private final List<Library> jarMods;
|
||||
|
||||
public MultiMCInstancePatch(int formatVersion, String id, String version, AssetIndexInfo assetIndex, String minecraftArguments, List<String> jvmArgs, String mainClass, int[] javaMajors, Library mainJar, List<String> traits, List<String> tweakers, List<Library> libraries0, List<Library> libraries1, List<Library> mavenFiles, List<Library> jarMods) {
|
||||
@SerializedName("requires")
|
||||
private final List<MultiMCManifest.MultiMCManifestCachedRequires> requires;
|
||||
|
||||
public MultiMCInstancePatch(int formatVersion, String id, String version, AssetIndexInfo assetIndex, String minecraftArguments, List<String> jvmArgs, String mainClass, int[] javaMajors, Library mainJar, List<String> traits, List<String> tweakers, List<Library> libraries0, List<Library> libraries1, List<Library> mavenFiles, List<Library> jarMods, List<MultiMCManifest.MultiMCManifestCachedRequires> requires) {
|
||||
this.formatVersion = formatVersion;
|
||||
this.id = id;
|
||||
this.version = version;
|
||||
@@ -112,6 +115,7 @@ public final class MultiMCInstancePatch {
|
||||
this.libraries1 = libraries1;
|
||||
this.mavenFiles = mavenFiles;
|
||||
this.jarMods = jarMods;
|
||||
this.requires = requires;
|
||||
}
|
||||
|
||||
public int getFormatVersion() {
|
||||
@@ -177,16 +181,22 @@ public final class MultiMCInstancePatch {
|
||||
return nonNullOrEmpty(jarMods);
|
||||
}
|
||||
|
||||
public List<MultiMCManifest.MultiMCManifestCachedRequires> getRequires() {
|
||||
return nonNullOrEmpty(requires);
|
||||
}
|
||||
|
||||
private static <T> List<T> nonNullOrEmpty(List<T> value) {
|
||||
return value != null && !value.isEmpty() ? value : Collections.emptyList();
|
||||
}
|
||||
|
||||
private static <T, K> List<T> dropDuplicate(List<T> original, Function<T, K> mapper) {
|
||||
Set<K> values = new HashSet<>();
|
||||
private static <T> List<T> dropDuplicate(List<T> original) {
|
||||
// TODO: Maybe new ArrayList(new LinkedHashSet(original)) ?
|
||||
|
||||
Set<T> values = new HashSet<>();
|
||||
List<T> result = new ArrayList<>();
|
||||
|
||||
for (T item : original) {
|
||||
if (values.add(mapper.apply(item))) {
|
||||
if (values.add(item)) {
|
||||
result.add(item);
|
||||
}
|
||||
}
|
||||
@@ -257,6 +267,8 @@ public final class MultiMCInstancePatch {
|
||||
}
|
||||
|
||||
for (MultiMCInstancePatch patch : patches) {
|
||||
Objects.requireNonNull(patch, "patch");
|
||||
|
||||
if (patch.getFormatVersion() != 1) {
|
||||
throw new UnsupportedOperationException(
|
||||
String.format("Unsupported JSON-Patch[%s] format version: %d", patch.getID(), patch.getFormatVersion())
|
||||
@@ -274,6 +286,8 @@ public final class MultiMCInstancePatch {
|
||||
Library mainJar;
|
||||
List<String> traits;
|
||||
List<String> tweakers;
|
||||
/* TODO: MultiMC use a slightly different way to store jars containing jni files.
|
||||
Transforming them to Official Scheme might boost compatibility with other launchers. */
|
||||
List<Library> libraries;
|
||||
List<Library> mavenOnlyFiles;
|
||||
List<String> jarModFileNames;
|
||||
@@ -327,10 +341,9 @@ public final class MultiMCInstancePatch {
|
||||
}
|
||||
}
|
||||
|
||||
traits = dropDuplicate(traits, Function.identity());
|
||||
tweakers = dropDuplicate(tweakers, Function.identity());
|
||||
libraries = dropDuplicate(libraries, Library::getName);
|
||||
jarModFileNames = dropDuplicate(jarModFileNames, Function.identity());
|
||||
traits = dropDuplicate(traits);
|
||||
tweakers = dropDuplicate(tweakers);
|
||||
jarModFileNames = dropDuplicate(jarModFileNames);
|
||||
|
||||
for (String tweaker : tweakers) {
|
||||
minecraftArguments.add("--tweakClass");
|
||||
|
||||
@@ -86,7 +86,7 @@ public final class MultiMCManifest {
|
||||
return equalsVersion;
|
||||
}
|
||||
|
||||
public String getUid() {
|
||||
public String getID() {
|
||||
return uid;
|
||||
}
|
||||
|
||||
|
||||
+75
-16
@@ -44,16 +44,26 @@ import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* <p>A task transforming MultiMC Modpack Scheme to Official Launcher Scheme.
|
||||
* The transforming process contains 7 stage:
|
||||
* General Setup, Load Components, Resolve Json-Patch, Build Artifact,
|
||||
* Copy Embedded Files, Assemble Game, Download Game and Apply JAR mods.
|
||||
* <ul>
|
||||
* <li>General Setup</li>
|
||||
* <li>Load Components</li>
|
||||
* <li>Resolve Json-Patch</li>
|
||||
* <li>Build Artifact</li>
|
||||
* <li>Copy Embedded Files</li>
|
||||
* <li>Assemble Game</li>
|
||||
* <li>Download Game</li>
|
||||
* <li>Apply JAR mods</li>
|
||||
* </ul>
|
||||
* See codes below for detailed implementation.
|
||||
* </p>
|
||||
*/
|
||||
@@ -64,7 +74,6 @@ public final class MultiMCModpackInstallTask extends Task<MultiMCInstancePatch.R
|
||||
private final MultiMCInstanceConfiguration manifest;
|
||||
private final String name;
|
||||
private final DefaultGameRepository repository;
|
||||
private final List<Task<MultiMCInstancePatch>> patches = new ArrayList<>();
|
||||
private final List<Task<?>> dependents = new ArrayList<>();
|
||||
private final List<Task<?>> dependencies = new ArrayList<>();
|
||||
private final DefaultDependencyManager dependencyManager;
|
||||
@@ -125,29 +134,74 @@ public final class MultiMCModpackInstallTask extends Task<MultiMCInstancePatch.R
|
||||
try (FileSystem fs = openModpack()) {
|
||||
Path root = getRootPath(fs);
|
||||
|
||||
List<Task<MultiMCInstancePatch>> patches = new ArrayList<>();
|
||||
for (MultiMCManifest.MultiMCManifestComponent component : Objects.requireNonNull(
|
||||
Objects.requireNonNull(manifest.getMmcPack(), "mmc-pack.json").getComponents(), "components"
|
||||
)) {
|
||||
String componentID = Objects.requireNonNull(component.getUid(), "Component ID");
|
||||
Path patchPath = root.resolve(String.format("patches/%s.json", componentID));
|
||||
|
||||
Task<String> task;
|
||||
if (Files.exists(patchPath)) {
|
||||
if (!Files.isRegularFile(patchPath)) {
|
||||
throw new IllegalArgumentException("Json-Patch isn't a file: " + componentID);
|
||||
}
|
||||
|
||||
// TODO: Task.completed has unclear compatibility issue.
|
||||
String text = FileUtils.readText(patchPath, StandardCharsets.UTF_8);
|
||||
task = Task.supplyAsync(() -> text);
|
||||
MultiMCInstancePatch patch = MultiMCInstancePatch.read(componentID, FileUtils.readText(patchPath, StandardCharsets.UTF_8));
|
||||
patches.add(Task.supplyAsync(() -> patch)); // TODO: Task.completed has unclear compatibility issue.
|
||||
} else {
|
||||
task = new GetTask(MultiMCComponents.getMetaURL(componentID, component.getVersion()));
|
||||
patches.add(
|
||||
new GetTask(MultiMCComponents.getMetaURL(componentID, component.getVersion()))
|
||||
.thenApplyAsync(s -> MultiMCInstancePatch.read(componentID, s))
|
||||
);
|
||||
}
|
||||
}
|
||||
dependents.add(new MMCInstancePatchesAssembleTask(patches));
|
||||
}
|
||||
}
|
||||
|
||||
private static final class MMCInstancePatchesAssembleTask extends Task<List<MultiMCInstancePatch>> {
|
||||
private final List<Task<MultiMCInstancePatch>> patches;
|
||||
|
||||
public MMCInstancePatchesAssembleTask(List<Task<MultiMCInstancePatch>> patches) {
|
||||
this.patches = patches;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends Task<?>> getDependents() {
|
||||
return patches;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
Map<String, MultiMCInstancePatch> existed = new HashMap<>();
|
||||
for (Task<MultiMCInstancePatch> patch : patches) {
|
||||
MultiMCInstancePatch result = patch.getResult();
|
||||
|
||||
existed.put(result.getID(), result);
|
||||
}
|
||||
|
||||
checking:
|
||||
while (true) {
|
||||
for (MultiMCInstancePatch patch : existed.values()) {
|
||||
for (MultiMCManifest.MultiMCManifestCachedRequires require : patch.getRequires()) {
|
||||
String componentID = require.getID();
|
||||
if (!existed.containsKey(componentID)) {
|
||||
Task<MultiMCInstancePatch> task = new GetTask(MultiMCComponents.getMetaURL(
|
||||
componentID, Lang.requireNonNullElse(require.getEqualsVersion(), require.getSuggests())
|
||||
)).thenApplyAsync(s -> MultiMCInstancePatch.read(componentID, s));
|
||||
task.run();
|
||||
|
||||
MultiMCInstancePatch result = Objects.requireNonNull(task.getResult());
|
||||
existed.put(result.getID(), result);
|
||||
continue checking;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Task<MultiMCInstancePatch> task2 = task.thenApplyAsync(s -> MultiMCInstancePatch.read(componentID, s));
|
||||
patches.add(task2);
|
||||
dependents.add(task2);
|
||||
break;
|
||||
}
|
||||
|
||||
setResult(new ArrayList<>(existed.values()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,10 +214,15 @@ public final class MultiMCModpackInstallTask extends Task<MultiMCInstancePatch.R
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
// Stage #3: Build Json-Patch artifact.
|
||||
MultiMCInstancePatch.ResolvedInstance artifact = MultiMCInstancePatch.resolveArtifact(patches.stream()
|
||||
.map(value -> Objects.requireNonNull(value.getResult(), "MultiMCInstancePatch"))
|
||||
.collect(Collectors.toList()), name
|
||||
);
|
||||
MultiMCInstancePatch.ResolvedInstance artifact = null;
|
||||
for (int i = dependents.size() - 1; i >= 0; i--) {
|
||||
Task<?> task = dependents.get(i);
|
||||
if (task instanceof MMCInstancePatchesAssembleTask) {
|
||||
artifact = MultiMCInstancePatch.resolveArtifact(((MMCInstancePatchesAssembleTask) task).getResult(), name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Objects.requireNonNull(artifact, "artifact");
|
||||
|
||||
// Stage #4: Copy embedded files.
|
||||
try (FileSystem fs = openModpack()) {
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ public final class MacOSHardwareDetector extends HardwareDetector {
|
||||
|
||||
try {
|
||||
Map<String, String> values = SystemUtils.run(Arrays.asList("/usr/sbin/sysctl", "machdep.cpu"),
|
||||
inputStream -> KeyValuePairUtils.loadProperties(
|
||||
inputStream -> KeyValuePairUtils.loadPairs(
|
||||
new BufferedReader(new InputStreamReader(inputStream, OperatingSystem.NATIVE_CHARSET))));
|
||||
|
||||
String brandString = values.get("machdep.cpu.brand_string");
|
||||
|
||||
@@ -813,4 +813,20 @@
|
||||
1.21.5-rc1
|
||||
1.21.5-rc2
|
||||
1.21.5
|
||||
25w14craftmine
|
||||
25w14craftmine
|
||||
25w15a
|
||||
25w16a
|
||||
25w17a
|
||||
25w18a
|
||||
25w19a
|
||||
25w20a
|
||||
25w21a
|
||||
1.21.6-pre1
|
||||
1.21.6-pre2
|
||||
1.21.6-pre3
|
||||
1.21.6-pre4
|
||||
1.21.6-rc1
|
||||
1.21.6
|
||||
1.21.7-rc1
|
||||
1.21.7-rc2
|
||||
1.21.7
|
||||
@@ -47,42 +47,42 @@ public class CrashReportAnalyzerTest {
|
||||
@Test
|
||||
public void jdk9() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/java9.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/java9.txt")),
|
||||
CrashReportAnalyzer.Rule.JDK_9);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jadeForestOptifine() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/jade_forest_optifine.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/jade_forest_optifine.txt")),
|
||||
CrashReportAnalyzer.Rule.JADE_FOREST_OPTIFINE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rtssForestSodium() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/crash-report/rtss_forest_sodium.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/crash-report/rtss_forest_sodium.txt")),
|
||||
CrashReportAnalyzer.Rule.RTSS_FOREST_SODIUM);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jvm32() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/jvm_32bit.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/jvm_32bit.txt")),
|
||||
CrashReportAnalyzer.Rule.JVM_32BIT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jvm321() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/jvm_32bit2.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/jvm_32bit2.txt")),
|
||||
CrashReportAnalyzer.Rule.JVM_32BIT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void modResolution() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/mod_resolution.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/mod_resolution.txt")),
|
||||
CrashReportAnalyzer.Rule.MOD_RESOLUTION);
|
||||
assertEquals(("Errors were found!\n" +
|
||||
" - Mod test depends on mod {fabricloader @ [>=0.11.3]}, which is missing!\n" +
|
||||
@@ -94,7 +94,7 @@ public class CrashReportAnalyzerTest {
|
||||
@Test
|
||||
public void forgemodResolution() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/forgemod_resolution.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/forgemod_resolution.txt")),
|
||||
CrashReportAnalyzer.Rule.FORGEMOD_RESOLUTION);
|
||||
assertEquals(("\tMod ID: 'vampirism', Requested by: 'werewolves', Expected range: '[1.9.0-beta.1,)', Actual version: '[MISSING]'\n").replaceAll("\\s+", ""),
|
||||
result.getMatcher().group("reason").replaceAll("\\s+", ""));
|
||||
@@ -103,7 +103,7 @@ public class CrashReportAnalyzerTest {
|
||||
@Test
|
||||
public void forgeFoundDuplicateMods() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/forge_found_duplicate_mods.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/forge_found_duplicate_mods.txt")),
|
||||
CrashReportAnalyzer.Rule.FORGE_FOUND_DUPLICATE_MODS);
|
||||
assertEquals(("\tMod ID: 'jei' from mod files: REIPluginCompatibilities-forge-12.0.93.jar, jei-1.20.1-forge-15.2.0.27.jar\n").replaceAll("\\s+", ""),
|
||||
result.getMatcher().group("reason").replaceAll("\\s+", ""));
|
||||
@@ -112,7 +112,7 @@ public class CrashReportAnalyzerTest {
|
||||
@Test
|
||||
public void modResolutionCollection() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/mod_resolution_collection.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/mod_resolution_collection.txt")),
|
||||
CrashReportAnalyzer.Rule.MOD_RESOLUTION_COLLECTION);
|
||||
assertEquals("tabtps-fabric", result.getMatcher().group("sourcemod"));
|
||||
assertEquals("{fabricloader @ [>=0.11.1]}", result.getMatcher().group("destmod"));
|
||||
@@ -121,7 +121,7 @@ public class CrashReportAnalyzerTest {
|
||||
@Test
|
||||
public void forgeEroor() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/crash-report/forge_error.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/crash-report/forge_error.txt")),
|
||||
CrashReportAnalyzer.Rule.FORGE_ERROR);
|
||||
assertEquals(("\nnet.minecraftforge.fml.common.MissingModsException: Mod pixelmon (Pixelmon) requires [forge@[14.23.5.2860,)]\n" +
|
||||
"\tat net.minecraftforge.fml.common.Loader.sortModList(Loader.java:264) ~[Loader.class:?]\n" +
|
||||
@@ -148,14 +148,14 @@ public class CrashReportAnalyzerTest {
|
||||
@Test
|
||||
public void modResolution0() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/crash-report/mod_resolution0.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/crash-report/mod_resolution0.txt")),
|
||||
CrashReportAnalyzer.Rule.MOD_RESOLUTION0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tooOldJava() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/too_old_java.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/too_old_java.txt")),
|
||||
CrashReportAnalyzer.Rule.TOO_OLD_JAVA);
|
||||
assertEquals("60", result.getMatcher().group("expected"));
|
||||
}
|
||||
@@ -163,7 +163,7 @@ public class CrashReportAnalyzerTest {
|
||||
@Test
|
||||
public void tooOldJava1() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/crash-report/too_old_java.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/crash-report/too_old_java.txt")),
|
||||
CrashReportAnalyzer.Rule.TOO_OLD_JAVA);
|
||||
assertEquals("52", result.getMatcher().group("expected"));
|
||||
}
|
||||
@@ -171,7 +171,7 @@ public class CrashReportAnalyzerTest {
|
||||
@Test
|
||||
public void tooOldJava2() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/crash-report/too_old_java2.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/crash-report/too_old_java2.txt")),
|
||||
CrashReportAnalyzer.Rule.TOO_OLD_JAVA);
|
||||
assertEquals("52", result.getMatcher().group("expected"));
|
||||
}
|
||||
@@ -179,14 +179,14 @@ public class CrashReportAnalyzerTest {
|
||||
@Test
|
||||
public void javaVersionIsTooHigh() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/java_version_is_too_high.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/java_version_is_too_high.txt")),
|
||||
CrashReportAnalyzer.Rule.JAVA_VERSION_IS_TOO_HIGH);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void securityException() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/crash-report/security.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/crash-report/security.txt")),
|
||||
CrashReportAnalyzer.Rule.FILE_CHANGED);
|
||||
assertEquals("assets/minecraft/texts/splashes.txt", result.getMatcher().group("file"));
|
||||
}
|
||||
@@ -194,7 +194,7 @@ public class CrashReportAnalyzerTest {
|
||||
@Test
|
||||
public void noClassDefFoundError1() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/crash-report/no_class_def_found_error.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/crash-report/no_class_def_found_error.txt")),
|
||||
CrashReportAnalyzer.Rule.NO_CLASS_DEF_FOUND_ERROR);
|
||||
assertEquals("blk", result.getMatcher().group("class"));
|
||||
}
|
||||
@@ -202,7 +202,7 @@ public class CrashReportAnalyzerTest {
|
||||
@Test
|
||||
public void noClassDefFoundError2() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/crash-report/no_class_def_found_error2.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/crash-report/no_class_def_found_error2.txt")),
|
||||
CrashReportAnalyzer.Rule.NO_CLASS_DEF_FOUND_ERROR);
|
||||
assertEquals("cer", result.getMatcher().group("class"));
|
||||
}
|
||||
@@ -210,7 +210,7 @@ public class CrashReportAnalyzerTest {
|
||||
@Test
|
||||
public void fileAlreadyExists() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/crash-report/file_already_exists.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/crash-report/file_already_exists.txt")),
|
||||
CrashReportAnalyzer.Rule.FILE_ALREADY_EXISTS);
|
||||
assertEquals(
|
||||
"D:\\Games\\Minecraft\\Minecraft Longtimeusing\\.minecraft\\versions\\1.12.2-forge1.12.2-14.23.5.2775\\config\\pvpsettings.txt",
|
||||
@@ -220,7 +220,7 @@ public class CrashReportAnalyzerTest {
|
||||
@Test
|
||||
public void loaderExceptionModCrash() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/crash-report/loader_exception_mod_crash.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/crash-report/loader_exception_mod_crash.txt")),
|
||||
CrashReportAnalyzer.Rule.LOADING_CRASHED_FORGE);
|
||||
assertEquals("Better PvP", result.getMatcher().group("name"));
|
||||
assertEquals("xaerobetterpvp", result.getMatcher().group("id"));
|
||||
@@ -229,7 +229,7 @@ public class CrashReportAnalyzerTest {
|
||||
@Test
|
||||
public void loaderExceptionModCrash2() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/crash-report/loader_exception_mod_crash2.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/crash-report/loader_exception_mod_crash2.txt")),
|
||||
CrashReportAnalyzer.Rule.LOADING_CRASHED_FORGE);
|
||||
assertEquals("Inventory Sort", result.getMatcher().group("name"));
|
||||
assertEquals("invsort", result.getMatcher().group("id"));
|
||||
@@ -238,7 +238,7 @@ public class CrashReportAnalyzerTest {
|
||||
@Test
|
||||
public void loaderExceptionModCrash3() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/crash-report/loader_exception_mod_crash3.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/crash-report/loader_exception_mod_crash3.txt")),
|
||||
CrashReportAnalyzer.Rule.LOADING_CRASHED_FORGE);
|
||||
assertEquals("SuperOres", result.getMatcher().group("name"));
|
||||
assertEquals("superores", result.getMatcher().group("id"));
|
||||
@@ -247,7 +247,7 @@ public class CrashReportAnalyzerTest {
|
||||
@Test
|
||||
public void loaderExceptionModCrash4() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/crash-report/loader_exception_mod_crash4.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/crash-report/loader_exception_mod_crash4.txt")),
|
||||
CrashReportAnalyzer.Rule.LOADING_CRASHED_FORGE);
|
||||
assertEquals("Kathairis", result.getMatcher().group("name"));
|
||||
assertEquals("kathairis", result.getMatcher().group("id"));
|
||||
@@ -256,7 +256,7 @@ public class CrashReportAnalyzerTest {
|
||||
@Test
|
||||
public void loadingErrorFabric() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/crash-report/loading_error_fabric.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/crash-report/loading_error_fabric.txt")),
|
||||
CrashReportAnalyzer.Rule.LOADING_CRASHED_FABRIC);
|
||||
assertEquals("test", result.getMatcher().group("id"));
|
||||
}
|
||||
@@ -264,56 +264,56 @@ public class CrashReportAnalyzerTest {
|
||||
@Test
|
||||
public void graphicsDriver() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/crash-report/graphics_driver.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/crash-report/graphics_driver.txt")),
|
||||
CrashReportAnalyzer.Rule.GRAPHICS_DRIVER);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void graphicsDriverJVM() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/graphics_driver.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/graphics_driver.txt")),
|
||||
CrashReportAnalyzer.Rule.GRAPHICS_DRIVER);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void splashScreen() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/crash-report/splashscreen.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/crash-report/splashscreen.txt")),
|
||||
CrashReportAnalyzer.Rule.GRAPHICS_DRIVER);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void macosFailedToFindServicePortForDisplay() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/macos_failed_to_find_service_port_for_display.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/macos_failed_to_find_service_port_for_display.txt")),
|
||||
CrashReportAnalyzer.Rule.MACOS_FAILED_TO_FIND_SERVICE_PORT_FOR_DISPLAY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void modName() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/mod_name.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/mod_name.txt")),
|
||||
CrashReportAnalyzer.Rule.MOD_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void openj9() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/openj9.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/openj9.txt")),
|
||||
CrashReportAnalyzer.Rule.OPENJ9);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolutionTooHigh() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/crash-report/resourcepack_resolution.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/crash-report/resourcepack_resolution.txt")),
|
||||
CrashReportAnalyzer.Rule.RESOLUTION_TOO_HIGH);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bootstrapFailed() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/bootstrap.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/bootstrap.txt")),
|
||||
CrashReportAnalyzer.Rule.BOOTSTRAP_FAILED);
|
||||
assertEquals("prefab", result.getMatcher().group("id"));
|
||||
}
|
||||
@@ -321,7 +321,7 @@ public class CrashReportAnalyzerTest {
|
||||
@Test
|
||||
public void mixinApplyModFailed() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/mixin_apply_mod_failed.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/mixin_apply_mod_failed.txt")),
|
||||
CrashReportAnalyzer.Rule.MIXIN_APPLY_MOD_FAILED);
|
||||
assertEquals("enhancedblockentities", result.getMatcher().group("id"));
|
||||
}
|
||||
@@ -329,7 +329,7 @@ public class CrashReportAnalyzerTest {
|
||||
@Test
|
||||
public void unsatisfiedLinkError() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/unsatisfied_link_error.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/unsatisfied_link_error.txt")),
|
||||
CrashReportAnalyzer.Rule.UNSATISFIED_LINK_ERROR);
|
||||
assertEquals("lwjgl.dll", result.getMatcher().group("name"));
|
||||
}
|
||||
@@ -337,35 +337,35 @@ public class CrashReportAnalyzerTest {
|
||||
@Test
|
||||
public void outOfMemoryMC() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/crash-report/out_of_memory.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/crash-report/out_of_memory.txt")),
|
||||
CrashReportAnalyzer.Rule.OUT_OF_MEMORY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void outOfMemoryJVM() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/out_of_memory.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/out_of_memory.txt")),
|
||||
CrashReportAnalyzer.Rule.OUT_OF_MEMORY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void outOfMemoryJVM1() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/out_of_memory2.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/out_of_memory2.txt")),
|
||||
CrashReportAnalyzer.Rule.OUT_OF_MEMORY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void memoryExceeded() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/memory_exceeded.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/memory_exceeded.txt")),
|
||||
CrashReportAnalyzer.Rule.MEMORY_EXCEEDED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void config() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/crash-report/config.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/crash-report/config.txt")),
|
||||
CrashReportAnalyzer.Rule.CONFIG);
|
||||
assertEquals("jumbofurnace", result.getMatcher().group("id"));
|
||||
assertEquals("jumbofurnace-server.toml", result.getMatcher().group("file"));
|
||||
@@ -374,7 +374,7 @@ public class CrashReportAnalyzerTest {
|
||||
@Test
|
||||
public void fabricWarnings() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/fabric_warnings.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/fabric_warnings.txt")),
|
||||
CrashReportAnalyzer.Rule.FABRIC_WARNINGS);
|
||||
assertEquals((" - Conflicting versions found for fabric-api-base: used 0.3.0+a02b446313, also found 0.3.0+a02b44633d, 0.3.0+a02b446318\n" +
|
||||
" - Conflicting versions found for fabric-rendering-data-attachment-v1: used 0.1.5+a02b446313, also found 0.1.5+a02b446318\n" +
|
||||
@@ -390,7 +390,7 @@ public class CrashReportAnalyzerTest {
|
||||
@Test
|
||||
public void fabricWarnings1() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/fabric_warnings2.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/fabric_warnings2.txt")),
|
||||
CrashReportAnalyzer.Rule.FABRIC_WARNINGS);
|
||||
assertEquals(("net.fabricmc.loader.impl.FormattedException: Mod resolution encountered an incompatible mod set!\n" +
|
||||
"A potential solution has been determined:\n" +
|
||||
@@ -404,7 +404,7 @@ public class CrashReportAnalyzerTest {
|
||||
@Test
|
||||
public void fabricWarnings2() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/fabric_warnings3.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/fabric_warnings3.txt")),
|
||||
CrashReportAnalyzer.Rule.FABRIC_WARNINGS);
|
||||
assertEquals(("net.fabricmc.loader.impl.FormattedException: Some of your mods are incompatible with the game or each other!\n" +
|
||||
"确定了一种可能的解决方法,这样做可能会解决你的问题:\n" +
|
||||
@@ -420,7 +420,7 @@ public class CrashReportAnalyzerTest {
|
||||
@Test
|
||||
public void fabricConflicts() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/fabric-mod-conflict.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/fabric-mod-conflict.txt")),
|
||||
CrashReportAnalyzer.Rule.MOD_RESOLUTION_CONFLICT);
|
||||
assertEquals("phosphor", result.getMatcher().group("sourcemod"));
|
||||
assertEquals("{starlight @ [*]}", result.getMatcher().group("destmod"));
|
||||
@@ -429,7 +429,7 @@ public class CrashReportAnalyzerTest {
|
||||
@Test
|
||||
public void fabricMissing() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/fabric-mod-missing.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/fabric-mod-missing.txt")),
|
||||
CrashReportAnalyzer.Rule.MOD_RESOLUTION_MISSING);
|
||||
assertEquals("pca", result.getMatcher().group("sourcemod"));
|
||||
assertEquals("{fabric @ [>=0.39.2]}", result.getMatcher().group("destmod"));
|
||||
@@ -438,35 +438,35 @@ public class CrashReportAnalyzerTest {
|
||||
@Test
|
||||
public void fabric0_12() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/fabric-version-0.12.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/fabric-version-0.12.txt")),
|
||||
CrashReportAnalyzer.Rule.FABRIC_VERSION_0_12);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void twilightForestOptiFineIncompatible() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/crash-report/mod/twilightforest_optifine_incompatibility.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/crash-report/mod/twilightforest_optifine_incompatibility.txt")),
|
||||
CrashReportAnalyzer.Rule.TWILIGHT_FOREST_OPTIFINE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void performantOptiFineIncompatible() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/crash-report/mod/performant_optifine_incompatibility.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/crash-report/mod/performant_optifine_incompatibility.txt")),
|
||||
CrashReportAnalyzer.Rule.PERFORMANT_FOREST_OPTIFINE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void neoforgeForestOptiFineIncompatible() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/crash-report/mod/neoforgeforest_optifine_incompatibility.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/crash-report/mod/neoforgeforest_optifine_incompatibility.txt")),
|
||||
CrashReportAnalyzer.Rule.NEOFORGE_FOREST_OPTIFINE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fabricMissingMinecraft() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/fabric-minecraft.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/fabric-minecraft.txt")),
|
||||
CrashReportAnalyzer.Rule.MOD_RESOLUTION_MISSING_MINECRAFT);
|
||||
assertEquals("fabric", result.getMatcher().group("mod"));
|
||||
assertEquals("[~1.16.2-alpha.20.28.a]", result.getMatcher().group("version"));
|
||||
@@ -475,161 +475,161 @@ public class CrashReportAnalyzerTest {
|
||||
@Test
|
||||
public void optifineRepeatInstallation() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/optifine_repeat_installation.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/optifine_repeat_installation.txt")),
|
||||
CrashReportAnalyzer.Rule.OPTIFINE_REPEAT_INSTALLATION);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void incompleteForgeInstallation() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/incomplete_forge_installation.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/incomplete_forge_installation.txt")),
|
||||
CrashReportAnalyzer.Rule.INCOMPLETE_FORGE_INSTALLATION);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void incompleteForgeInstallation2() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/incomplete_forge_installation2.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/incomplete_forge_installation2.txt")),
|
||||
CrashReportAnalyzer.Rule.INCOMPLETE_FORGE_INSTALLATION);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void incompleteForgeInstallation3() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/incomplete_forge_installation3.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/incomplete_forge_installation3.txt")),
|
||||
CrashReportAnalyzer.Rule.INCOMPLETE_FORGE_INSTALLATION);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void incompleteForgeInstallation4() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/incomplete_forge_installation4.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/incomplete_forge_installation4.txt")),
|
||||
CrashReportAnalyzer.Rule.INCOMPLETE_FORGE_INSTALLATION);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void incompleteForgeInstallation5() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/incomplete_forge_installation5.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/incomplete_forge_installation5.txt")),
|
||||
CrashReportAnalyzer.Rule.INCOMPLETE_FORGE_INSTALLATION);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void incompleteForgeInstallation6() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/incomplete_forge_installation6.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/incomplete_forge_installation6.txt")),
|
||||
CrashReportAnalyzer.Rule.INCOMPLETE_FORGE_INSTALLATION);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void incompleteForgeInstallation7() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/incomplete_forge_installation7.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/incomplete_forge_installation7.txt")),
|
||||
CrashReportAnalyzer.Rule.INCOMPLETE_FORGE_INSTALLATION);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forgeRepeatInstallation() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/forge_repeat_installation.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/forge_repeat_installation.txt")),
|
||||
CrashReportAnalyzer.Rule.FORGE_REPEAT_INSTALLATION);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forgeRepeatInstallation1() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/forge_repeat_installation2.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/forge_repeat_installation2.txt")),
|
||||
CrashReportAnalyzer.Rule.FORGE_REPEAT_INSTALLATION);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void needJDK11() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/crash-report/need_jdk11.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/crash-report/need_jdk11.txt")),
|
||||
CrashReportAnalyzer.Rule.NEED_JDK11);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void needJDK112() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/crash-report/need_jdk112.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/crash-report/need_jdk112.txt")),
|
||||
CrashReportAnalyzer.Rule.NEED_JDK11);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void needJDK113() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/crash-report/need_jdk113.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/crash-report/need_jdk113.txt")),
|
||||
CrashReportAnalyzer.Rule.NEED_JDK11);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void optifineIsNotCompatibleWithForge() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/optifine_is_not_compatible_with_forge.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/optifine_is_not_compatible_with_forge.txt")),
|
||||
CrashReportAnalyzer.Rule.OPTIFINE_IS_NOT_COMPATIBLE_WITH_FORGE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void optifineIsNotCompatibleWithForge1() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/optifine_is_not_compatible_with_forge2.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/optifine_is_not_compatible_with_forge2.txt")),
|
||||
CrashReportAnalyzer.Rule.OPTIFINE_IS_NOT_COMPATIBLE_WITH_FORGE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void optifineIsNotCompatibleWithForge2() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/optifine_is_not_compatible_with_forge3.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/optifine_is_not_compatible_with_forge3.txt")),
|
||||
CrashReportAnalyzer.Rule.OPTIFINE_IS_NOT_COMPATIBLE_WITH_FORGE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void optifineIsNotCompatibleWithForge3() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/optifine_is_not_compatible_with_forge4.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/optifine_is_not_compatible_with_forge4.txt")),
|
||||
CrashReportAnalyzer.Rule.OPTIFINE_IS_NOT_COMPATIBLE_WITH_FORGE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void optifineIsNotCompatibleWithForge4() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/optifine_is_not_compatible_with_forge5.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/optifine_is_not_compatible_with_forge5.txt")),
|
||||
CrashReportAnalyzer.Rule.OPTIFINE_IS_NOT_COMPATIBLE_WITH_FORGE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void optifineIsNotCompatibleWithForge5() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/optifine_is_not_compatible_with_forge6.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/optifine_is_not_compatible_with_forge6.txt")),
|
||||
CrashReportAnalyzer.Rule.OPTIFINE_IS_NOT_COMPATIBLE_WITH_FORGE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shadersMod() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/shaders_mod.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/shaders_mod.txt")),
|
||||
CrashReportAnalyzer.Rule.SHADERS_MOD);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void installMixinbootstrap() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/logs/install_mixinbootstrap.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/logs/install_mixinbootstrap.txt")),
|
||||
CrashReportAnalyzer.Rule.INSTALL_MIXINBOOTSTRAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nightconfigfixes() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/crash-report/night_config_fixes.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/crash-report/night_config_fixes.txt")),
|
||||
CrashReportAnalyzer.Rule.NIGHT_CONFIG_FIXES);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customNpc() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/crash-report/mod/customnpc.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/crash-report/mod/customnpc.txt")),
|
||||
CrashReportAnalyzer.Rule.ENTITY);
|
||||
assertEquals("customnpcs.CustomNpc (noppes.npcs.entity.EntityCustomNpc)",
|
||||
result.getMatcher().group("type"));
|
||||
@@ -644,7 +644,7 @@ public class CrashReportAnalyzerTest {
|
||||
@Test
|
||||
public void tconstruct() throws IOException {
|
||||
CrashReportAnalyzer.Result result = findResultByRule(
|
||||
CrashReportAnalyzer.anaylze(loadLog("/crash-report/mod/tconstruct.txt")),
|
||||
CrashReportAnalyzer.analyze(loadLog("/crash-report/mod/tconstruct.txt")),
|
||||
CrashReportAnalyzer.Rule.BLOCK);
|
||||
assertEquals("Block{tconstruct:seared_drain}[active=true,facing=north]",
|
||||
result.getMatcher().group("type"));
|
||||
|
||||
+4
-4
@@ -4,13 +4,13 @@
|
||||
|
||||
| | Windows | Linux | macOS | FreeBSD |
|
||||
|----------------------------|:--------------------------------------------------|:---------------------------|:------------------------------------------------------------------------|:---------------------------|
|
||||
| x86-64 | ✅️ | ✅️ | ✅️ | 👌 (Minecraft 1.13~1.21.4) |
|
||||
| x86-64 | ✅️ | ✅️ | ✅️ | 👌 (Minecraft 1.13~1.21.7) |
|
||||
| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / |
|
||||
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.4) | 👌 (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.7) | 👌 (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.4) | / | / |
|
||||
| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.4) | / | / |
|
||||
| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.7) | / | / |
|
||||
| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.7) | / | / |
|
||||
| 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.4) |
|
||||
| x86-64 | ✅️ | ✅️ | ✅️ | 👌(Minecraft 1.13~1.21.7) |
|
||||
| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / |
|
||||
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.4) | 👌 (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.7) | 👌 (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.4) | / | / |
|
||||
| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.4) | / | / |
|
||||
| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.7) | / | / |
|
||||
| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.7) | / | / |
|
||||
| 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.4) |
|
||||
| x86-64 | ✅️ | ✅️ | ✅️ | 👌(Minecraft 1.13~1.21.7) |
|
||||
| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / |
|
||||
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.4) | 👌 (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.7) | 👌 (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.4) | / | / |
|
||||
| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.4) | / | / |
|
||||
| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.7) | / | / |
|
||||
| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.7) | / | / |
|
||||
| LoongArch64 (舊世界) | / | 👌 (Minecraft 1.6~1.20.1) | / | / |
|
||||
| PowerPC-64 (Little-Endian) | / | ❔ | / | / |
|
||||
| S390x | / | ❔ | / | / |
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 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.gradle.mod;
|
||||
|
||||
import com.google.gson.*;
|
||||
@@ -24,6 +41,9 @@ import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public abstract class ParseModDataTask extends DefaultTask {
|
||||
|
||||
@InputFile
|
||||
@@ -44,15 +64,12 @@ public abstract class ParseModDataTask extends DefaultTask {
|
||||
Pattern.compile("^/projects/(?<modid>[\\w-]+)(/(.*?))?$"),
|
||||
Pattern.compile("^/mc-mods/minecraft/(?<modid>[\\w-]+)(/(.*?))?$"),
|
||||
Pattern.compile("^/legacy/mc-mods/minecraft/(\\d+)-(?<modid>[\\w-]+)"),
|
||||
Pattern.compile("^//minecraft/mc-mods/(?<modid>eyes-mod)$") // Workaround for Eye Blocks
|
||||
};
|
||||
|
||||
private static String parseCurseforge(String url) {
|
||||
URI res = URI.create(url);
|
||||
|
||||
if (!"http".equals(res.getScheme())
|
||||
&& !"https".equals(res.getScheme())
|
||||
&& !"www.curseforge.com".equals(res.getHost())) { // Workaround for DakerACG
|
||||
if (!"http".equals(res.getScheme()) && !"https".equals(res.getScheme())) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -77,10 +94,10 @@ public abstract class ParseModDataTask extends DefaultTask {
|
||||
return "";
|
||||
}
|
||||
|
||||
private static final Set<String> skip = new HashSet<>(Arrays.asList(
|
||||
private static final Set<String> skip = Set.of(
|
||||
"Minecraft",
|
||||
"The Building Game"
|
||||
));
|
||||
);
|
||||
|
||||
@TaskAction
|
||||
public void run() throws IOException {
|
||||
|
||||
Reference in new issue
Block a user