Compare commits
131
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
578aaafb0a | ||
|
|
60deea64c8 | ||
|
|
7e1cd4319b | ||
|
|
318da840f9 | ||
|
|
0c7046d764 | ||
|
|
bdf4886445 | ||
|
|
4a758d045d | ||
|
|
1059a4b3d4 | ||
|
|
a25eb14da4 | ||
|
|
349b663c83 | ||
|
|
04781da3a7 | ||
|
|
96e1ddff79 | ||
|
|
e8502890a4 | ||
|
|
4d0619a9df | ||
|
|
fba14737f9 | ||
|
|
5de98399c5 | ||
|
|
7adfa0e3d1 | ||
|
|
a45b636b1c | ||
|
|
50c22e1f43 | ||
|
|
3b38c22398 | ||
|
|
618243350e | ||
|
|
ff4940e79f | ||
|
|
1c163934ee | ||
|
|
c513d00c50 | ||
|
|
2c9549b36b | ||
|
|
3389e0a934 | ||
|
|
78149449a9 | ||
|
|
b792b23ad4 | ||
|
|
881b0e9127 | ||
|
|
77e06c8820 | ||
|
|
bc2405cf12 | ||
|
|
72b5428f8b | ||
|
|
e7e8d45497 | ||
|
|
82dbe18d78 | ||
|
|
ecb7ef0867 | ||
|
|
d40742a624 | ||
|
|
6f8002a0cf | ||
|
|
3d3896c5c4 | ||
|
|
a24f8e2ec0 | ||
|
|
fce08a6923 | ||
|
|
5fa01c8838 | ||
|
|
15a3fb5f59 | ||
|
|
d5ab14f476 | ||
|
|
554ecfd6d5 | ||
|
|
c2abe755f4 | ||
|
|
c0410c46ff | ||
|
|
da93b4c12d | ||
|
|
c16d6e8d5f | ||
|
|
6df73aeb68 | ||
|
|
23d11eca81 | ||
|
|
0e307f3b71 | ||
|
|
33b791f2b0 | ||
|
|
de01946ab2 | ||
|
|
5526bac70e | ||
|
|
741f65d37c | ||
|
|
ecafc9d6f0 | ||
|
|
8795d522b0 | ||
|
|
d8e7e0e97e | ||
|
|
8236dfc7e5 | ||
|
|
8a15b7d274 | ||
|
|
fce4c13515 | ||
|
|
2f36506099 | ||
|
|
5e60eaf595 | ||
|
|
f70fb4ae08 | ||
|
|
38adb48051 | ||
|
|
f972c49762 | ||
|
|
5afeb3d019 | ||
|
|
38897fbd2a | ||
|
|
0562042218 | ||
|
|
c5e7ee8aef | ||
|
|
35b288b800 | ||
|
|
a609d32081 | ||
|
|
72d8b75643 | ||
|
|
01f9a452f1 | ||
|
|
f2edc8001c | ||
|
|
d2df515325 | ||
|
|
95e39fe87a | ||
|
|
662bf1a9d4 | ||
|
|
bfda297838 | ||
|
|
d80f61e2e7 | ||
|
|
1f7b156423 | ||
|
|
56d20a5d08 | ||
|
|
b5cea83a8a | ||
|
|
397edaec5d | ||
|
|
39b8de4824 | ||
|
|
5197157a97 | ||
|
|
d61826ba4c | ||
|
|
85886da770 | ||
|
|
6f53da159a | ||
|
|
78e15d130b | ||
|
|
7d12ef6b09 | ||
|
|
260f4b6c5d | ||
|
|
104534d12f | ||
|
|
81b3911dc9 | ||
|
|
c5b8aabc57 | ||
|
|
f0a064afdd | ||
|
|
8a4f6af193 | ||
|
|
e63ee49eaa | ||
|
|
d5408c38b3 | ||
|
|
5fd52bb808 | ||
|
|
cb2b3525b8 | ||
|
|
4c6fdf49de | ||
|
|
981b55a10e | ||
|
|
8460df7d5d | ||
|
|
727e6746d2 | ||
|
|
8e0f0e2a38 | ||
|
|
3df52b0e8f | ||
|
|
6f0e133bb0 | ||
|
|
36da64f796 | ||
|
|
4c242d0c80 | ||
|
|
0a230ef6b5 | ||
|
|
c12ae9fdf8 | ||
|
|
773ef4282c | ||
|
|
9edfe29659 | ||
|
|
5591d92e25 | ||
|
|
65bdb29e71 | ||
|
|
355df211db | ||
|
|
fa66549f53 | ||
|
|
6e48dbd6c6 | ||
|
|
e3c7278759 | ||
|
|
dd1829d8ca | ||
|
|
60ac16a3ea | ||
|
|
b400a9a3ef | ||
|
|
c7cc2d0733 | ||
|
|
861223e909 | ||
|
|
70ecbc734e | ||
|
|
2ca7e7c830 | ||
|
|
9a0db56ac3 | ||
|
|
c1ae5875ba | ||
|
|
1d0ae427bf | ||
|
|
3dde2d19e2 |
No files matched your search
@@ -1,6 +1,6 @@
|
||||
name: Bug 反馈 | Bug Feedback
|
||||
name: Bug 反馈 | Bug Report
|
||||
description:
|
||||
反馈一个 HMCL 错误。| Bug Feedback.
|
||||
反馈一个 HMCL 错误。| File a bug report for HMCL.
|
||||
title: "[Bug] "
|
||||
labels: bug
|
||||
body:
|
||||
@@ -8,29 +8,35 @@ body:
|
||||
attributes:
|
||||
value: |
|
||||
提交前请确认:
|
||||
* 该问题确实是 **HMCL 的错误**,而**不是 Minecraft 非正常退出**,如果你的 Minecraft 非正常退出,请前往 [QQ 群](https://docs.hmcl.net/groups.html)/[Discord 频道](https://discord.gg/jVvC7HfM6U) 中获取帮助。
|
||||
* 你的启动器版本是**最新的快照版本**,可以点击 [此处](https://zkitefly.github.io/HMCL-Snapshot-Update/) 下载最新快照版本。
|
||||
|
||||
如果你的问题并不属于上述两类,你可以选取另一种 Issue 类型,或者直接前往 [QQ 群](https://docs.hmcl.net/groups.html)/[Discord 频道](https://discord.gg/jVvC7HfM6U) 中获取帮助。
|
||||
|
||||
|
||||
* 该问题确实是 **HMCL 的错误**,而**不是 Minecraft 非正常退出**,如果你的 Minecraft 非正常退出,请前往 [QQ 群](https://docs.hmcl.net/groups.html)/[Discord 服务器](https://discord.gg/jVvC7HfM6U) 获取帮助。
|
||||
* 你的启动器版本是**最新的预览版本**,可以点击 [此处](https://zkitefly.github.io/HMCL-Snapshot-Update/) 下载最新预览版本。
|
||||
|
||||
如果你的问题并不属于上述两类,你可以选取另一种 Issue 类型,或者直接前往 [QQ 群](https://docs.hmcl.net/groups.html)/[Discord 服务器](https://discord.gg/jVvC7HfM6U) 获取帮助。
|
||||
|
||||
Before submitting, please confirm:
|
||||
* The issue is indeed **an error of HMCL**, not **Minecraft abnormal exit**. If your Minecraft exits abnormally, please go to the [QQ group](https://docs.hmcl.net/groups.html) or [Discord channel](https://discord.gg/jVvC7HfM6U) for help.
|
||||
* Your launcher version is **the latest snapshot version**. You can click [here](https://github.com/burningtnt/HMCL-Snapshot-Update/raw/master/datas/HMCL-dev.jar) to download the latest snapshot version.
|
||||
|
||||
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 channel](https://discord.gg/jVvC7HfM6U) for help.
|
||||
|
||||
* The issue is indeed **a bug of HMCL**, not **Minecraft abnormal exit**. If your Minecraft exits abnormally, please go to the [QQ group](https://docs.hmcl.net/groups.html) or [Discord server](https://discord.gg/jVvC7HfM6U) for help.
|
||||
* 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: 请输入您遇到 BUG 的平台。 Please enter the platform on which you encountered the bug.
|
||||
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:
|
||||
label: 问题描述 | Problem Description
|
||||
description: 请尽可能地详细描述你所遇到的问题,并描述如何重新触发这个问题。 Please describe the problem you met in as much detail as possible. Besides, please describe how to trigger this problem again.
|
||||
label: 问题描述 | Bug Description
|
||||
description: |
|
||||
请尽可能地详细描述你所遇到的问题,并描述如何重新触发这个问题。
|
||||
Please describe the bug you met in as much detail as possible. Additionally, describe the steps to reproduce this bug.
|
||||
placeholder: |
|
||||
1. 点击 HMCL 上的某个按钮 | Click a button named ...
|
||||
2. 向下翻页 | Scroll down
|
||||
@@ -42,12 +48,12 @@ body:
|
||||
attributes:
|
||||
label: 启动器崩溃报告 / 启动器日志文件 | Launcher Crash Report / Launcher Log File
|
||||
description: |
|
||||
如果您的启动器崩溃了,请将崩溃报告填入(或将文件拖入)下方。
|
||||
如果您的启动器没有崩溃,请在遇到问题后不要退出启动器,在启动器的【设置】>【通用】>【调试】一栏中点击“导出启动器日志”,并将导出的日志拖到下方的输入栏中。
|
||||
如果你的启动器崩溃了,请将崩溃报告填入(或将文件拖入)下方。
|
||||
如果你的启动器没有崩溃,请在遇到问题后**不要退出启动器**,在启动器的 “设置 → 通用 → 调试” 一栏中点击 “导出启动器日志”,并将导出的日志拖到下方的输入栏中。
|
||||
**请注意:启动器崩溃报告或日志文件是诊断问题的重要依据,请务必上传!**
|
||||
|
||||
If your launcher crashes, please fill it in (or drag the file in) below.
|
||||
If your launcher doesn't crash, please DO NOT EXIT your launcher, click "Export Launcher Log" in the [Settings] > [General] > [Debug] column of the launcher, and drag the exported log to the input field below.
|
||||
**Attention: The crash report or the log file is the key to solve the problem. Please update it!**
|
||||
If your launcher crashes, please fill in (or drag the file in) the following input field with the crash report.
|
||||
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
|
||||
@@ -1,11 +1,11 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: QQ 群 | QQ Groups
|
||||
- name: QQ 群 | QQ Group
|
||||
url: https://docs.hmcl.net/groups.html
|
||||
about: Hello Minecraft! Launcher 的官方 QQ 交流群。| The official QQ groups of Hello Minecraft! Laucher.
|
||||
- name: Discord 频道 | Discord Channel
|
||||
about: Hello Minecraft! Launcher 的官方 QQ 交流群。| The official QQ group of Hello Minecraft! Launcher.
|
||||
- name: Discord 服务器 | Discord Server
|
||||
url: https://discord.gg/jVvC7HfM6U
|
||||
about: Hello Minecraft! Launcher 的官方 Discord 频道。| The official Discord channel of Hello Minecraft! Launcher.
|
||||
about: Hello Minecraft! Launcher 的官方 Discord 服务器。| The official Discord server of Hello Minecraft! Launcher.
|
||||
- name: 其他反馈 | Others
|
||||
url: https://github.com/HMCL-dev/HMCL/discussions/new/choose
|
||||
about: 通过 Discussions 反馈其他问题。| Report other problems in Discussions.
|
||||
@@ -1,22 +1,28 @@
|
||||
name: 新功能 | Feature Request
|
||||
description: 为 HMCL 提出新功能 | A new feature for HMCL
|
||||
description: 为 HMCL 提出新功能。| Suggest a new feature or enhancement for HMCL.
|
||||
title: "[Feature] "
|
||||
labels: enhancement
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: 请确认 Issues 列表无重复的项目。| Please make sure that no duplicated issues has already been delivered.
|
||||
value: |
|
||||
请确认 Issues 列表无重复的项目。
|
||||
Please make sure that no duplicate issues have already been submitted.
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: 描述 | Description
|
||||
description: 请详细描述你想加入的新功能。| Please describe the new feature detaily.
|
||||
description: |
|
||||
请详细描述你想加入的新功能。
|
||||
Please describe the new feature in detail.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reason
|
||||
attributes:
|
||||
label: 原因 | Reason
|
||||
description: 请描述该功能带来的好处及原因。| Please describe why you want to add the feature into HMCL.
|
||||
description: |
|
||||
请描述该功能带来的好处及原因。
|
||||
Please describe why you want to add the feature or enhancement to HMCL.
|
||||
validations:
|
||||
required: true
|
||||
@@ -50,16 +50,19 @@ jobs:
|
||||
if: ${{ env.continue == 'true' }}
|
||||
run: |
|
||||
echo "The full changelogs can be found on the website: https://docs.hmcl.net/changelog/dev.html" >> RELEASE_NOTE
|
||||
echo "Notice: changelogs are written in Chinese." >> RELEASE_NOTE
|
||||
echo "" >> RELEASE_NOTE
|
||||
echo "*Notice: changelogs are written in Chinese.*" >> RELEASE_NOTE
|
||||
echo "" >> RELEASE_NOTE
|
||||
echo "| File Name | SHA-256 Checksum |" >> RELEASE_NOTE
|
||||
echo "| --- | --- |" >> RELEASE_NOTE
|
||||
echo "| HMCL-$HMCL_VERSION.exe | $(cat HMCL-$HMCL_VERSION.exe.sha256) |" >> RELEASE_NOTE
|
||||
echo "| HMCL-$HMCL_VERSION.jar | $(cat HMCL-$HMCL_VERSION.jar.sha256) |" >> RELEASE_NOTE
|
||||
echo "| HMCL-$HMCL_VERSION.sh | $(cat HMCL-$HMCL_VERSION.sh.sha256) |" >> RELEASE_NOTE
|
||||
echo "| [HMCL-$HMCL_VERSION.exe]($GH_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.exe) | \`$(cat HMCL-$HMCL_VERSION.exe.sha256)\` |" >> RELEASE_NOTE
|
||||
echo "| [HMCL-$HMCL_VERSION.jar]($GH_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.jar) | \`$(cat HMCL-$HMCL_VERSION.jar.sha256)\` |" >> RELEASE_NOTE
|
||||
echo "| [HMCL-$HMCL_VERSION.sh]($GH_DOWNLOAD_BASE_URL/v$HMCL_VERSION/HMCL-$HMCL_VERSION.sh) | \`$(cat HMCL-$HMCL_VERSION.sh.sha256)\` |" >> RELEASE_NOTE
|
||||
env:
|
||||
GH_DOWNLOAD_BASE_URL: https://github.com/HMCL-dev/HMCL/releases/download
|
||||
- name: Create release
|
||||
if: ${{ env.continue == 'true' }}
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
body_path: RELEASE_NOTE
|
||||
files: |
|
||||
@@ -69,6 +72,7 @@ jobs:
|
||||
target_commitish: ${{ env.HMCL_COMMIT_SHA }}
|
||||
name: ${{ env.HMCL_TAG_NAME }}
|
||||
tag_name: ${{ env.HMCL_TAG_NAME }}
|
||||
prerelease: true
|
||||
stable-check-update:
|
||||
if: ${{ github.repository_owner == 'HMCL-dev' }}
|
||||
needs: dev-check-update
|
||||
@@ -114,16 +118,18 @@ jobs:
|
||||
echo "" >> RELEASE_NOTE
|
||||
echo "The full changelogs can be found on the website: https://docs.hmcl.net/changelog/stable.html" >> RELEASE_NOTE
|
||||
echo "" >> RELEASE_NOTE
|
||||
echo "Notice: changelogs are written in Chinese." >> RELEASE_NOTE
|
||||
echo "*Notice: changelogs are written in Chinese.*" >> RELEASE_NOTE
|
||||
echo "" >> RELEASE_NOTE
|
||||
echo "| File Name | SHA-256 Checksum |" >> RELEASE_NOTE
|
||||
echo "| --- | --- |" >> RELEASE_NOTE
|
||||
echo "| HMCL-$HMCL_VERSION.exe | $(cat HMCL-$HMCL_VERSION.exe.sha256) |" >> RELEASE_NOTE
|
||||
echo "| HMCL-$HMCL_VERSION.jar | $(cat HMCL-$HMCL_VERSION.jar.sha256) |" >> RELEASE_NOTE
|
||||
echo "| HMCL-$HMCL_VERSION.sh | $(cat HMCL-$HMCL_VERSION.sh.sha256) |" >> RELEASE_NOTE
|
||||
echo "| [HMCL-$HMCL_VERSION.exe]($GH_DOWNLOAD_BASE_URL/release-$HMCL_VERSION/HMCL-$HMCL_VERSION.exe) | \`$(cat HMCL-$HMCL_VERSION.exe.sha256)\` |" >> RELEASE_NOTE
|
||||
echo "| [HMCL-$HMCL_VERSION.jar]($GH_DOWNLOAD_BASE_URL/release-$HMCL_VERSION/HMCL-$HMCL_VERSION.jar) | \`$(cat HMCL-$HMCL_VERSION.jar.sha256)\` |" >> RELEASE_NOTE
|
||||
echo "| [HMCL-$HMCL_VERSION.sh]($GH_DOWNLOAD_BASE_URL/release-$HMCL_VERSION/HMCL-$HMCL_VERSION.sh) | \`$(cat HMCL-$HMCL_VERSION.sh.sha256)\` |" >> RELEASE_NOTE
|
||||
env:
|
||||
GH_DOWNLOAD_BASE_URL: https://github.com/HMCL-dev/HMCL/releases/download
|
||||
- name: Create release
|
||||
if: ${{ env.continue == 'true' }}
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
body_path: RELEASE_NOTE
|
||||
files: |
|
||||
|
||||
@@ -20,6 +20,7 @@ minecraft-exported-crash-info*
|
||||
/HMCLCore/build/
|
||||
/HMCLTransformerDiscoveryService/build/
|
||||
/minecraft/libraries/HMCLTransformerDiscoveryService/build/
|
||||
/buildSrc/build/
|
||||
|
||||
# idea
|
||||
.idea
|
||||
|
||||
+3
-1
@@ -1,2 +1,4 @@
|
||||
/data.csv
|
||||
/data.json
|
||||
/data.json
|
||||
/mod.json
|
||||
/modpack.json
|
||||
+19
-3
@@ -1,3 +1,4 @@
|
||||
import org.jackhuang.hmcl.gradle.mod.ParseModDataTask
|
||||
import java.net.URI
|
||||
import java.nio.file.FileSystems
|
||||
import java.nio.file.Files
|
||||
@@ -25,7 +26,7 @@ val buildNumber = System.getenv("BUILD_NUMBER")?.toInt().let { number ->
|
||||
if (!shortCommit.isNullOrEmpty()) "$prefix-$shortCommit" else "SNAPSHOT"
|
||||
}
|
||||
}
|
||||
val versionRoot = System.getenv("VERSION_ROOT") ?: "3.5"
|
||||
val versionRoot = System.getenv("VERSION_ROOT") ?: "3.6"
|
||||
val versionType = System.getenv("VERSION_TYPE") ?: if (isOfficial) "nightly" else "unofficial"
|
||||
|
||||
val microsoftAuthId = System.getenv("MICROSOFT_AUTH_ID") ?: ""
|
||||
@@ -37,13 +38,13 @@ version = "$versionRoot.$buildNumber"
|
||||
dependencies {
|
||||
implementation(project(":HMCLCore"))
|
||||
implementation("libs:JFoenix")
|
||||
implementation("com.twelvemonkeys.imageio:imageio-webp:3.12.0")
|
||||
}
|
||||
|
||||
fun digest(algorithm: String, bytes: ByteArray): ByteArray = MessageDigest.getInstance(algorithm).digest(bytes)
|
||||
|
||||
fun createChecksum(file: File) {
|
||||
val algorithms = linkedMapOf(
|
||||
"MD5" to "md5",
|
||||
"SHA-1" to "sha1",
|
||||
"SHA-256" to "sha256",
|
||||
"SHA-512" to "sha512"
|
||||
@@ -111,6 +112,9 @@ tasks.getByName<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("sha
|
||||
exclude("**/package-info.class")
|
||||
exclude("META-INF/maven/**")
|
||||
|
||||
exclude("META-INF/services/javax.imageio.spi.ImageReaderSpi")
|
||||
exclude("META-INF/services/javax.imageio.spi.ImageInputStreamSpi")
|
||||
|
||||
minimize {
|
||||
exclude(dependency("com.google.code.gson:.*:.*"))
|
||||
exclude(dependency("libs:JFoenix:.*"))
|
||||
@@ -118,7 +122,7 @@ tasks.getByName<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("sha
|
||||
|
||||
manifest {
|
||||
attributes(
|
||||
"Created-By" to "Copyright(c) 2013-2024 huangyuhui.",
|
||||
"Created-By" to "Copyright(c) 2013-2025 huangyuhui.",
|
||||
"Main-Class" to "org.jackhuang.hmcl.Main",
|
||||
"Multi-Release" to "true",
|
||||
"Implementation-Version" to project.version,
|
||||
@@ -250,3 +254,15 @@ tasks.create<JavaExec>("run") {
|
||||
logger.quiet("HMCL_JAVA_OPTS: $vmOptions")
|
||||
}
|
||||
}
|
||||
|
||||
// mcmod data
|
||||
|
||||
tasks.register<ParseModDataTask>("parseModData") {
|
||||
inputFile.set(layout.projectDirectory.file("mod.json"))
|
||||
outputFile.set(layout.projectDirectory.file("src/main/resources/assets/mod_data.txt"))
|
||||
}
|
||||
|
||||
tasks.register<ParseModDataTask>("parseModPackData") {
|
||||
inputFile.set(layout.projectDirectory.file("modpack.json"))
|
||||
outputFile.set(layout.projectDirectory.file("src/main/resources/assets/modpack_data.txt"))
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import json
|
||||
import codecs
|
||||
import re
|
||||
import sys
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
|
||||
S = ';'
|
||||
|
||||
MOD_SEPARATOR = ','
|
||||
|
||||
CURSEFORGE_PATTERN1 = re.compile(
|
||||
r'^/(minecraft|Minecraft|minecraft-bedrock)/(mc-mods|modpacks|customization|mc-addons|texture-packs|customization/configuration|addons)/+(?P<modid>[\w-]+)(/(.*?))?$')
|
||||
CURSEFORGE_PATTERN2 = re.compile(
|
||||
r'^/projects/(?P<modid>[\w-]+)(/(.*?))?$')
|
||||
CURSEFORGE_PATTERN3 = re.compile(
|
||||
r'^/mc-mods/minecraft/(?P<modid>[\w-]+)(/(.*?))?$')
|
||||
CURSEFORGE_PATTERN4 = re.compile(
|
||||
r'^/legacy/mc-mods/minecraft/(\d+)-(?P<modid>[\w-]+)'
|
||||
)
|
||||
|
||||
|
||||
def parseCurseforge(url):
|
||||
res = urlparse(url)
|
||||
if res.scheme not in ['http', 'https']:
|
||||
return ''
|
||||
for pattern in [CURSEFORGE_PATTERN1, CURSEFORGE_PATTERN2, CURSEFORGE_PATTERN3, CURSEFORGE_PATTERN4]:
|
||||
match = pattern.match(res.path)
|
||||
if match:
|
||||
return match.group('modid')
|
||||
return ''
|
||||
|
||||
|
||||
MCMOD_PATTERN = re.compile(
|
||||
r'^http(s)?://www\.mcmod\.cn/(class|modpack)/(?P<modid>\d+)\.html$')
|
||||
|
||||
|
||||
def parseMcmod(url):
|
||||
match = MCMOD_PATTERN.match(url)
|
||||
if match:
|
||||
return match.group('modid')
|
||||
return ''
|
||||
|
||||
skip = [
|
||||
'Minecraft',
|
||||
'The Building Game'
|
||||
]
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
json_name = sys.argv[1] or 'data.json'
|
||||
|
||||
with codecs.open(json_name, mode='r', encoding='utf-8-sig') as jsonfile, codecs.open('data.csv', mode='w', encoding='utf-8') as outfile:
|
||||
data = json.load(jsonfile)
|
||||
|
||||
for mod in data:
|
||||
chinese_name = mod['name']['main']
|
||||
sub_name = mod['name']['sub']
|
||||
abbr = mod['name']['abbr']
|
||||
|
||||
if sub_name in skip:
|
||||
continue
|
||||
|
||||
if S in chinese_name:
|
||||
print('Error! ' + chinese_name)
|
||||
exit(1)
|
||||
|
||||
if S in sub_name:
|
||||
print('Error! ' + chinese_name)
|
||||
exit(1)
|
||||
|
||||
black_lists = [
|
||||
'Wood Converter'
|
||||
]
|
||||
|
||||
curseforge_id = ''
|
||||
mcmod_id = ''
|
||||
links = mod['links']['list']
|
||||
if 'curseforge' in links and links['curseforge'] and sub_name not in black_lists and chinese_name not in black_lists:
|
||||
for link in links['curseforge']:
|
||||
curseforge_id = parseCurseforge(link['url'])
|
||||
if curseforge_id != '':
|
||||
break
|
||||
if curseforge_id == '':
|
||||
print('Error curseforge ' + chinese_name)
|
||||
exit(1)
|
||||
if 'mcmod' in links and links['mcmod']:
|
||||
mcmod_id = parseMcmod(links['mcmod'][0]['url'])
|
||||
if mcmod_id == '':
|
||||
print('Error mcmod ' + chinese_name)
|
||||
exit(1)
|
||||
|
||||
mod_id = []
|
||||
if 'modid' in mod and 'list' in mod['modid']:
|
||||
for id in mod['modid']['list']:
|
||||
if MOD_SEPARATOR in id:
|
||||
print('Error mod id!' + id)
|
||||
exit(1)
|
||||
|
||||
mod_id.append(id)
|
||||
mod_ids = MOD_SEPARATOR.join([str(id) for id in mod_id])
|
||||
|
||||
outfile.write(
|
||||
f'{curseforge_id}{S}{mcmod_id}{S}{mod_ids}{S}{chinese_name}{S}{sub_name}{S}{abbr}\n')
|
||||
|
||||
print('Success!')
|
||||
@@ -225,8 +225,11 @@ public final class Launcher extends Application {
|
||||
LOG.info("Operating System: " + (OperatingSystem.OS_RELEASE_PRETTY_NAME == null
|
||||
? OperatingSystem.SYSTEM_NAME + ' ' + OperatingSystem.SYSTEM_VERSION
|
||||
: OperatingSystem.OS_RELEASE_PRETTY_NAME + " (" + OperatingSystem.SYSTEM_NAME + ' ' + OperatingSystem.SYSTEM_VERSION + ')'));
|
||||
LOG.info("System Architecture: " + Architecture.SYSTEM_ARCH_NAME);
|
||||
LOG.info("Java Architecture: " + Architecture.CURRENT_ARCH_NAME);
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
LOG.info("Processor Identifier: " + System.getenv("PROCESSOR_IDENTIFIER"));
|
||||
}
|
||||
LOG.info("System Architecture: " + Architecture.SYSTEM_ARCH.getDisplayName());
|
||||
LOG.info("Java Architecture: " + Architecture.CURRENT_ARCH.getDisplayName());
|
||||
LOG.info("Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor"));
|
||||
LOG.info("Java VM Version: " + System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.info") + "), " + System.getProperty("java.vm.vendor"));
|
||||
LOG.info("Java Home: " + System.getProperty("java.home"));
|
||||
@@ -245,7 +248,6 @@ public final class Launcher extends Application {
|
||||
LOG.info("XDG Session Type: " + System.getenv("XDG_SESSION_TYPE"));
|
||||
LOG.info("XDG Current Desktop: " + System.getenv("XDG_CURRENT_DESKTOP"));
|
||||
}
|
||||
|
||||
launch(Launcher.class, args);
|
||||
} catch (Throwable e) { // Fucking JavaFX will suppress the exception and will break our crash reporter.
|
||||
CRASH_REPORTER.uncaughtException(Thread.currentThread(), e);
|
||||
|
||||
@@ -54,6 +54,7 @@ public final class Main {
|
||||
public static void main(String[] args) {
|
||||
System.getProperties().putIfAbsent("java.net.useSystemProxies", "true");
|
||||
System.getProperties().putIfAbsent("javafx.autoproxy.disable", "true");
|
||||
System.getProperties().putIfAbsent("javafx.animation.pulse", "120");
|
||||
System.getProperties().putIfAbsent("http.agent", "HMCL/" + Metadata.VERSION);
|
||||
|
||||
LOG.start(Metadata.HMCL_DIRECTORY.resolve("logs"));
|
||||
|
||||
@@ -43,8 +43,8 @@ public class CrashReport {
|
||||
stackTrace + "\n\n" +
|
||||
"-- System Details --\n" +
|
||||
" Operating System: " + OperatingSystem.SYSTEM_NAME + ' ' + OperatingSystem.SYSTEM_VERSION + "\n" +
|
||||
" System Architecture: " + Architecture.SYSTEM_ARCH_NAME + "\n" +
|
||||
" Java Architecture: " + Architecture.CURRENT_ARCH_NAME + "\n" +
|
||||
" System Architecture: " + Architecture.SYSTEM_ARCH.getDisplayName() + "\n" +
|
||||
" Java Architecture: " + Architecture.CURRENT_ARCH.getDisplayName() + "\n" +
|
||||
" Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor") + "\n" +
|
||||
" Java VM Version: " + System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.info") + "), " + System.getProperty("java.vm.vendor") + "\n" +
|
||||
" JVM Max Memory: " + Runtime.getRuntime().maxMemory() + "\n" +
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.ProxyManager;
|
||||
import org.jackhuang.hmcl.setting.VersionIconType;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
@@ -43,9 +44,7 @@ import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
@@ -218,7 +217,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
|
||||
private VersionSetting initLocalVersionSetting(String id, VersionSetting vs) {
|
||||
localVersionSettings.put(id, vs);
|
||||
vs.addPropertyChangedListener(a -> saveVersionSetting(id));
|
||||
vs.addListener(a -> saveVersionSetting(id));
|
||||
return vs;
|
||||
}
|
||||
|
||||
@@ -251,7 +250,6 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
public VersionSetting getVersionSetting(String id) {
|
||||
VersionSetting vs = getLocalVersionSetting(id);
|
||||
if (vs == null || vs.isUsesGlobal()) {
|
||||
profile.getGlobal().setGlobal(true); // always keep global.isGlobal = true
|
||||
profile.getGlobal().setUsesGlobal(true);
|
||||
return profile.getGlobal();
|
||||
} else
|
||||
@@ -261,24 +259,11 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
public Optional<File> getVersionIconFile(String id) {
|
||||
File root = getVersionRoot(id);
|
||||
|
||||
File iconFile = new File(root, "icon.png");
|
||||
if (iconFile.exists()) {
|
||||
return Optional.of(iconFile);
|
||||
}
|
||||
|
||||
iconFile = new File(root, "icon.jpg");
|
||||
if (iconFile.exists()) {
|
||||
return Optional.of(iconFile);
|
||||
}
|
||||
|
||||
iconFile = new File(root, "icon.bmp");
|
||||
if (iconFile.exists()) {
|
||||
return Optional.of(iconFile);
|
||||
}
|
||||
|
||||
iconFile = new File(root, "icon.gif");
|
||||
if (iconFile.exists()) {
|
||||
return Optional.of(iconFile);
|
||||
for (String extension : FXUtils.IMAGE_EXTENSIONS) {
|
||||
File file = new File(root, "icon." + extension);
|
||||
if (file.exists()) {
|
||||
return Optional.of(file);
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
@@ -286,7 +271,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
|
||||
public void setVersionIconFile(String id, File iconFile) throws IOException {
|
||||
String ext = FileUtils.getExtension(iconFile).toLowerCase(Locale.ROOT);
|
||||
if (!ext.equals("png") && !ext.equals("jpg") && !ext.equals("bmp") && !ext.equals("gif")) {
|
||||
if (!FXUtils.IMAGE_EXTENSIONS.contains(ext)) {
|
||||
throw new IllegalArgumentException("Unsupported icon file: " + ext);
|
||||
}
|
||||
|
||||
@@ -297,11 +282,9 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
|
||||
public void deleteIconFile(String id) {
|
||||
File root = getVersionRoot(id);
|
||||
|
||||
new File(root, "icon.png").delete();
|
||||
new File(root, "icon.jpg").delete();
|
||||
new File(root, "icon.bmp").delete();
|
||||
new File(root, "icon.gif").delete();
|
||||
for (String extension : FXUtils.IMAGE_EXTENSIONS) {
|
||||
new File(root, "icon." + extension).delete();
|
||||
}
|
||||
}
|
||||
|
||||
public Image getVersionIconImage(String id) {
|
||||
@@ -315,9 +298,9 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
Version version = getVersion(id).resolve(this);
|
||||
Optional<File> iconFile = getVersionIconFile(id);
|
||||
if (iconFile.isPresent()) {
|
||||
try (InputStream inputStream = new FileInputStream(iconFile.get())) {
|
||||
return new Image(inputStream);
|
||||
} catch (IOException e) {
|
||||
try {
|
||||
return FXUtils.loadImage(iconFile.get().toPath());
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to load version icon of " + id, e);
|
||||
}
|
||||
}
|
||||
@@ -353,6 +336,8 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
if (!FileUtils.makeDirectory(file.getAbsoluteFile().getParentFile()))
|
||||
return false;
|
||||
|
||||
LOG.info("Saving version setting: " + id);
|
||||
|
||||
try {
|
||||
FileUtils.writeText(file, GSON.toJson(localVersionSettings.get(id)));
|
||||
return true;
|
||||
@@ -373,7 +358,9 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
vs = createLocalVersionSetting(id);
|
||||
if (vs == null)
|
||||
return null;
|
||||
vs.setUsesGlobal(false);
|
||||
if (vs.isUsesGlobal()) {
|
||||
vs.setUsesGlobal(false);
|
||||
}
|
||||
return vs;
|
||||
}
|
||||
|
||||
@@ -383,7 +370,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
vs.setUsesGlobal(true);
|
||||
}
|
||||
|
||||
public LaunchOptions getLaunchOptions(String version, JavaRuntime javaVersion, File gameDir, List<String> javaAgents, boolean makeLaunchScript) {
|
||||
public LaunchOptions getLaunchOptions(String version, JavaRuntime javaVersion, File gameDir, List<String> javaAgents, List<String> javaArguments, boolean makeLaunchScript) {
|
||||
VersionSetting vs = getVersionSetting(version);
|
||||
|
||||
LaunchOptions.Builder builder = new LaunchOptions.Builder()
|
||||
@@ -426,7 +413,8 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
.setUseNativeGLFW(vs.isUseNativeGLFW())
|
||||
.setUseNativeOpenAL(vs.isUseNativeOpenAL())
|
||||
.setDaemon(!makeLaunchScript && vs.getLauncherVisibility().isDaemon())
|
||||
.setJavaAgents(javaAgents);
|
||||
.setJavaAgents(javaAgents)
|
||||
.setJavaArguments(javaArguments);
|
||||
if (config().hasProxy()) {
|
||||
builder.setProxy(ProxyManager.getProxy());
|
||||
if (config().hasProxyAuth()) {
|
||||
|
||||
@@ -36,10 +36,14 @@ import org.jackhuang.hmcl.mod.ModpackProvider;
|
||||
import org.jackhuang.hmcl.setting.*;
|
||||
import org.jackhuang.hmcl.task.*;
|
||||
import org.jackhuang.hmcl.ui.*;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.ui.construct.PromptDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.ResponseCodeException;
|
||||
import org.jackhuang.hmcl.util.platform.*;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
@@ -60,9 +64,10 @@ import static javafx.application.Platform.runLater;
|
||||
import static javafx.application.Platform.setImplicitExit;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.Lang.resolveException;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.platform.Platform.*;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.platform.Platform.SYSTEM_PLATFORM;
|
||||
import static org.jackhuang.hmcl.util.platform.Platform.isCompatibleWithX86Java;
|
||||
|
||||
public final class LauncherHelper {
|
||||
|
||||
@@ -126,13 +131,14 @@ public final class LauncherHelper {
|
||||
boolean integrityCheck = repository.unmarkVersionLaunchedAbnormally(selectedVersion);
|
||||
CountDownLatch launchingLatch = new CountDownLatch(1);
|
||||
List<String> javaAgents = new ArrayList<>(0);
|
||||
List<String> javaArguments = new ArrayList<>(0);
|
||||
|
||||
AtomicReference<JavaRuntime> javaVersionRef = new AtomicReference<>();
|
||||
|
||||
TaskExecutor executor = checkGameState(profile, setting, version.get())
|
||||
.thenComposeAsync(java -> {
|
||||
javaVersionRef.set(Objects.requireNonNull(java));
|
||||
version.set(NativePatcher.patchNative(version.get(), gameVersion.orElse(null), java, setting));
|
||||
version.set(NativePatcher.patchNative(repository, version.get(), gameVersion.orElse(null), java, setting, javaArguments));
|
||||
if (setting.isNotCheckGame())
|
||||
return null;
|
||||
return Task.allOf(
|
||||
@@ -150,7 +156,7 @@ public final class LauncherHelper {
|
||||
Task.composeAsync(() -> {
|
||||
Renderer renderer = setting.getRenderer();
|
||||
if (renderer != Renderer.DEFAULT && OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
Library lib = NativePatcher.getMesaLoader(java, renderer);
|
||||
Library lib = NativePatcher.getMesaLoader(java);
|
||||
if (lib == null)
|
||||
return null;
|
||||
File file = dependencyManager.getGameRepository().getLibraryFile(version.get(), lib);
|
||||
@@ -177,7 +183,11 @@ public final class LauncherHelper {
|
||||
.thenComposeAsync(() -> gameVersion.map(s -> new GameVerificationFixTask(dependencyManager, s, version.get())).orElse(null))
|
||||
.thenComposeAsync(() -> logIn(account).withStage("launch.state.logging_in"))
|
||||
.thenComposeAsync(authInfo -> Task.supplyAsync(() -> {
|
||||
LaunchOptions launchOptions = repository.getLaunchOptions(selectedVersion, javaVersionRef.get(), profile.getGameDir(), javaAgents, scriptFile != null);
|
||||
LaunchOptions launchOptions = repository.getLaunchOptions(
|
||||
selectedVersion, javaVersionRef.get(), profile.getGameDir(), javaAgents, javaArguments, scriptFile != null);
|
||||
|
||||
LOG.info("Here's the structure of game mod directory:\n" + FileUtils.printFileStructure(repository.getModManager(selectedVersion).getModsDirectory(), 10));
|
||||
|
||||
return new HMCLGameLauncher(
|
||||
repository,
|
||||
version.get(),
|
||||
@@ -522,6 +532,9 @@ public final class LauncherHelper {
|
||||
case MODDED_JAVA_17:
|
||||
suggestions.add(i18n("launch.advice.modded_java", 17, gameVersion));
|
||||
break;
|
||||
case MODDED_JAVA_21:
|
||||
suggestions.add(i18n("launch.advice.modded_java", 21, gameVersion));
|
||||
break;
|
||||
case VANILLA_JAVA_8_51:
|
||||
suggestions.add(i18n("launch.advice.java8_51_1_13"));
|
||||
break;
|
||||
|
||||
@@ -87,7 +87,7 @@ public final class TexturesLoader {
|
||||
}
|
||||
|
||||
private static final ThreadPoolExecutor POOL = threadPool("TexturesDownload", true, 2, 10, TimeUnit.SECONDS);
|
||||
private static final Path TEXTURES_DIR = Metadata.MINECRAFT_DIRECTORY.resolve("assets").resolve("skins");
|
||||
private static final Path TEXTURES_DIR = Metadata.HMCL_DIRECTORY.resolve("skins");
|
||||
|
||||
private static Path getTexturePath(Texture texture) {
|
||||
String url = texture.getUrl();
|
||||
|
||||
@@ -376,8 +376,8 @@ public final class JavaManager {
|
||||
searchAllJavaInDirectory(javaRuntimes, Paths.get(System.getProperty("user.home"), "/.sdkman/candidates/java")); // SDKMAN!
|
||||
break;
|
||||
case OSX:
|
||||
tryAddJavaHome(javaRuntimes, Paths.get("/Library/Java/JavaVirtualMachines/Contents/Home"));
|
||||
tryAddJavaHome(javaRuntimes, Paths.get(System.getProperty("user.home"), "/Library/Java/JavaVirtualMachines/Contents/Home"));
|
||||
searchJavaInMacJavaVirtualMachines(javaRuntimes, Paths.get("/Library/Java/JavaVirtualMachines"));
|
||||
searchJavaInMacJavaVirtualMachines(javaRuntimes, Paths.get(System.getProperty("user.home"), "/Library/Java/JavaVirtualMachines"));
|
||||
tryAddJavaExecutable(javaRuntimes, Paths.get("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java"));
|
||||
tryAddJavaExecutable(javaRuntimes, Paths.get("/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/MacOS/itms/java/bin/java"));
|
||||
// Homebrew
|
||||
@@ -631,6 +631,20 @@ public final class JavaManager {
|
||||
}
|
||||
}
|
||||
|
||||
private static void searchJavaInMacJavaVirtualMachines(Map<Path, JavaRuntime> javaRuntimes, Path directory) {
|
||||
if (!Files.isDirectory(directory)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory)) {
|
||||
for (Path subDir : stream) {
|
||||
tryAddJavaHome(javaRuntimes, subDir.resolve("Contents/Home"));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to find Java in " + directory, e);
|
||||
}
|
||||
}
|
||||
|
||||
// ==== Windows Registry Support ====
|
||||
private static void queryJavaInRegistryKey(Map<Path, JavaRuntime> javaRuntimes, String location) {
|
||||
for (String java : querySubFolders(location)) {
|
||||
|
||||
@@ -47,7 +47,7 @@ import java.net.Proxy;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
public final class Config implements Cloneable, Observable {
|
||||
public final class Config implements Observable {
|
||||
|
||||
public static final int CURRENT_UI_VERSION = 0;
|
||||
|
||||
@@ -223,11 +223,6 @@ public final class Config implements Cloneable, Observable {
|
||||
return CONFIG_GSON.toJson(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Config clone() {
|
||||
return fromJson(this.toJson());
|
||||
}
|
||||
|
||||
// Getters & Setters & Properties
|
||||
public String getSelectedProfile() {
|
||||
return selectedProfile.get();
|
||||
|
||||
@@ -197,7 +197,7 @@ public final class ConfigHolder {
|
||||
}
|
||||
}
|
||||
|
||||
static void markConfigDirty() {
|
||||
private static void markConfigDirty() {
|
||||
configWriter.accept(configInstance.toJson());
|
||||
}
|
||||
|
||||
@@ -241,11 +241,7 @@ public final class ConfigHolder {
|
||||
}
|
||||
}
|
||||
|
||||
static void markGlobalConfigDirty() {
|
||||
private static void markGlobalConfigDirty() {
|
||||
globalConfigWriter.accept(globalConfigInstance.toJson());
|
||||
}
|
||||
|
||||
private static void saveGlobalConfigSync() throws IOException {
|
||||
writeToConfig(globalConfigInstance.toJson());
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ import java.lang.reflect.Type;
|
||||
import java.util.*;
|
||||
|
||||
@JsonAdapter(GlobalConfig.Serializer.class)
|
||||
public final class GlobalConfig implements Cloneable, Observable {
|
||||
public final class GlobalConfig implements Observable {
|
||||
|
||||
@Nullable
|
||||
public static GlobalConfig fromJson(String json) throws JsonParseException {
|
||||
@@ -79,11 +79,6 @@ public final class GlobalConfig implements Cloneable, Observable {
|
||||
return Config.CONFIG_GSON.toJson(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalConfig clone() {
|
||||
return fromJson(this.toJson());
|
||||
}
|
||||
|
||||
public int getAgreementVersion() {
|
||||
return agreementVersion.get();
|
||||
}
|
||||
|
||||
@@ -188,7 +188,7 @@ public final class Profile implements Observable {
|
||||
global.addListener(listener);
|
||||
gameDir.addListener(listener);
|
||||
useRelativePath.addListener(listener);
|
||||
global.get().addPropertyChangedListener(listener);
|
||||
global.get().addListener(listener);
|
||||
selectedVersion.addListener(listener);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,10 @@ import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Font;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.Lazy;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
@@ -56,29 +59,41 @@ public class Theme {
|
||||
Color.web("#B71C1C") // red
|
||||
};
|
||||
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
private static Optional<Font> font;
|
||||
private static final Lazy<Font> FONT = new Lazy<>(() -> {
|
||||
String[] fileNames = {"font.ttf", "font.otf", "font.woff"};
|
||||
|
||||
private static Optional<Font> tryLoadFont() {
|
||||
//noinspection OptionalAssignedToNull
|
||||
if (font != null) {
|
||||
return font;
|
||||
}
|
||||
for (String fileName : fileNames) {
|
||||
Path path = Paths.get(fileName).toAbsolutePath();
|
||||
if (Files.isRegularFile(path)) {
|
||||
try {
|
||||
Font font = Font.loadFont(path.toUri().toURL().toExternalForm(), 0);
|
||||
if (font != null) {
|
||||
return font;
|
||||
}
|
||||
} catch (MalformedURLException ignored) {
|
||||
}
|
||||
|
||||
Path path = Paths.get("font.ttf");
|
||||
if (!Files.isRegularFile(path)) {
|
||||
path = Paths.get("font.otf");
|
||||
}
|
||||
|
||||
if (Files.isRegularFile(path)) {
|
||||
try {
|
||||
return font = Optional.ofNullable(Font.loadFont(path.toAbsolutePath().toUri().toURL().toExternalForm(), 0));
|
||||
} catch (MalformedURLException ignored) {
|
||||
LOG.warning("Failed to load font " + path);
|
||||
}
|
||||
}
|
||||
|
||||
return font = Optional.empty();
|
||||
}
|
||||
for (String fileName : fileNames) {
|
||||
Path path = Metadata.HMCL_DIRECTORY.resolve(fileName);
|
||||
if (Files.isRegularFile(path)) {
|
||||
try {
|
||||
Font font = Font.loadFont(path.toUri().toURL().toExternalForm(), 0);
|
||||
if (font != null) {
|
||||
return font;
|
||||
}
|
||||
} catch (MalformedURLException ignored) {
|
||||
}
|
||||
|
||||
LOG.warning("Failed to load font " + path);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
public static Theme getTheme() {
|
||||
Theme theme = config().getTheme();
|
||||
@@ -123,6 +138,45 @@ public class Theme {
|
||||
opacity);
|
||||
}
|
||||
|
||||
private static Pair<String, String> parseFontStyle(String style) {
|
||||
if (style == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
style = style.toLowerCase(Locale.ROOT);
|
||||
|
||||
String weight;
|
||||
String posture;
|
||||
|
||||
if (style.contains("thin")) {
|
||||
weight = "100";
|
||||
} else if (style.contains("extralight") || style.contains("extra light") || style.contains("ultralight") | style.contains("ultra light")) {
|
||||
weight = "200";
|
||||
} else if (style.contains("medium")) {
|
||||
weight = "500";
|
||||
} else if (style.contains("semibold") || style.contains("semi bold") || style.contains("demibold") || style.contains("demi bold")) {
|
||||
weight = "600";
|
||||
} else if (style.contains("extrabold") || style.contains("extra bold") || style.contains("ultrabold") || style.contains("ultra bold")) {
|
||||
weight = "800";
|
||||
} else if (style.contains("black") || style.contains("heavy")) {
|
||||
weight = "900";
|
||||
} else if (style.contains("light")) {
|
||||
weight = "lighter";
|
||||
} else if (style.contains("bold")) {
|
||||
weight = "bold";
|
||||
} else {
|
||||
weight = null;
|
||||
}
|
||||
|
||||
if (style.contains("italic") || style.contains("oblique")) {
|
||||
posture = "italic";
|
||||
} else {
|
||||
posture = null;
|
||||
}
|
||||
|
||||
return Pair.pair(weight, posture);
|
||||
}
|
||||
|
||||
public String[] getStylesheets(String overrideFontFamily) {
|
||||
String css = "/assets/css/blue.css";
|
||||
|
||||
@@ -130,12 +184,12 @@ public class Theme {
|
||||
? System.getProperty("hmcl.font.override", System.getenv("HMCL_FONT"))
|
||||
: overrideFontFamily;
|
||||
|
||||
String fontStyle = null;
|
||||
Pair<String, String> fontStyle = null;
|
||||
if (fontFamily == null) {
|
||||
Optional<Font> font = tryLoadFont();
|
||||
if (font.isPresent()) {
|
||||
fontFamily = font.get().getFamily();
|
||||
fontStyle = font.get().getStyle();
|
||||
Font font = FONT.get();
|
||||
if (font != null) {
|
||||
fontFamily = font.getFamily();
|
||||
fontStyle = parseFontStyle(font.getStyle());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,8 +213,13 @@ public class Theme {
|
||||
else
|
||||
themeBuilder.append("-fx-font-family:\"").append(fontFamily).append("\";");
|
||||
|
||||
if (fontStyle != null && !fontStyle.isEmpty())
|
||||
themeBuilder.append("-fx-font-style:\"").append(fontStyle).append("\";");
|
||||
if (fontStyle != null) {
|
||||
if (fontStyle.getKey() != null)
|
||||
themeBuilder.append("-fx-font-weight:").append(fontStyle.getKey()).append(";");
|
||||
|
||||
if (fontStyle.getValue() != null)
|
||||
themeBuilder.append("-fx-font-style:").append(fontStyle.getValue()).append(";");
|
||||
}
|
||||
|
||||
themeBuilder.append('}');
|
||||
|
||||
|
||||
@@ -20,11 +20,14 @@ package org.jackhuang.hmcl.setting;
|
||||
import com.google.gson.*;
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.*;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.java.JavaManager;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.ObservableHelper;
|
||||
import org.jackhuang.hmcl.util.javafx.PropertyUtils;
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
@@ -42,16 +45,12 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
* @author huangyuhui
|
||||
*/
|
||||
@JsonAdapter(VersionSetting.Serializer.class)
|
||||
public final class VersionSetting implements Cloneable {
|
||||
public final class VersionSetting implements Cloneable, Observable {
|
||||
|
||||
private boolean global = false;
|
||||
private transient ObservableHelper helper = new ObservableHelper(this);
|
||||
|
||||
public boolean isGlobal() {
|
||||
return global;
|
||||
}
|
||||
|
||||
public void setGlobal(boolean global) {
|
||||
this.global = global;
|
||||
public VersionSetting() {
|
||||
PropertyUtils.attachListener(this, helper);
|
||||
}
|
||||
|
||||
private final BooleanProperty usesGlobalProperty = new SimpleBooleanProperty(this, "usesGlobal", true);
|
||||
@@ -708,79 +707,21 @@ public final class VersionSetting implements Cloneable {
|
||||
}
|
||||
}
|
||||
|
||||
public void addPropertyChangedListener(InvalidationListener listener) {
|
||||
usesGlobalProperty.addListener(listener);
|
||||
javaVersionProperty.addListener(listener);
|
||||
javaDirProperty.addListener(listener);
|
||||
wrapperProperty.addListener(listener);
|
||||
permSizeProperty.addListener(listener);
|
||||
maxMemoryProperty.addListener(listener);
|
||||
minMemoryProperty.addListener(listener);
|
||||
autoMemory.addListener(listener);
|
||||
preLaunchCommandProperty.addListener(listener);
|
||||
postExitCommand.addListener(listener);
|
||||
javaArgsProperty.addListener(listener);
|
||||
minecraftArgsProperty.addListener(listener);
|
||||
environmentVariablesProperty.addListener(listener);
|
||||
noJVMArgsProperty.addListener(listener);
|
||||
notCheckGameProperty.addListener(listener);
|
||||
notCheckJVMProperty.addListener(listener);
|
||||
notPatchNativesProperty.addListener(listener);
|
||||
showLogsProperty.addListener(listener);
|
||||
serverIpProperty.addListener(listener);
|
||||
fullscreenProperty.addListener(listener);
|
||||
widthProperty.addListener(listener);
|
||||
heightProperty.addListener(listener);
|
||||
gameDirTypeProperty.addListener(listener);
|
||||
gameDirProperty.addListener(listener);
|
||||
processPriorityProperty.addListener(listener);
|
||||
rendererProperty.addListener(listener);
|
||||
useNativeGLFW.addListener(listener);
|
||||
useNativeOpenAL.addListener(listener);
|
||||
launcherVisibilityProperty.addListener(listener);
|
||||
defaultJavaPathProperty.addListener(listener);
|
||||
nativesDirProperty.addListener(listener);
|
||||
nativesDirTypeProperty.addListener(listener);
|
||||
versionIcon.addListener(listener);
|
||||
@Override
|
||||
public void addListener(InvalidationListener listener) {
|
||||
helper.addListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(InvalidationListener listener) {
|
||||
helper.removeListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VersionSetting clone() {
|
||||
VersionSetting versionSetting = new VersionSetting();
|
||||
versionSetting.setUsesGlobal(isUsesGlobal());
|
||||
versionSetting.setJavaVersionType(getJavaVersionType());
|
||||
versionSetting.setJavaVersion(getJavaVersion());
|
||||
versionSetting.setDefaultJavaPath(getDefaultJavaPath());
|
||||
versionSetting.setJavaDir(getJavaDir());
|
||||
versionSetting.setWrapper(getWrapper());
|
||||
versionSetting.setPermSize(getPermSize());
|
||||
versionSetting.setMaxMemory(getMaxMemory());
|
||||
versionSetting.setMinMemory(getMinMemory());
|
||||
versionSetting.setAutoMemory(isAutoMemory());
|
||||
versionSetting.setPreLaunchCommand(getPreLaunchCommand());
|
||||
versionSetting.setPostExitCommand(getPostExitCommand());
|
||||
versionSetting.setJavaArgs(getJavaArgs());
|
||||
versionSetting.setMinecraftArgs(getMinecraftArgs());
|
||||
versionSetting.setEnvironmentVariables(getEnvironmentVariables());
|
||||
versionSetting.setNoJVMArgs(isNoJVMArgs());
|
||||
versionSetting.setNotCheckGame(isNotCheckGame());
|
||||
versionSetting.setNotCheckJVM(isNotCheckJVM());
|
||||
versionSetting.setNotPatchNatives(isNotPatchNatives());
|
||||
versionSetting.setShowLogs(isShowLogs());
|
||||
versionSetting.setServerIp(getServerIp());
|
||||
versionSetting.setFullscreen(isFullscreen());
|
||||
versionSetting.setWidth(getWidth());
|
||||
versionSetting.setHeight(getHeight());
|
||||
versionSetting.setGameDirType(getGameDirType());
|
||||
versionSetting.setGameDir(getGameDir());
|
||||
versionSetting.setProcessPriority(getProcessPriority());
|
||||
versionSetting.setRenderer(getRenderer());
|
||||
versionSetting.setUseNativeGLFW(isUseNativeGLFW());
|
||||
versionSetting.setUseNativeOpenAL(isUseNativeOpenAL());
|
||||
versionSetting.setLauncherVisibility(getLauncherVisibility());
|
||||
versionSetting.setNativesDir(getNativesDir());
|
||||
versionSetting.setVersionIcon(getVersionIcon());
|
||||
return versionSetting;
|
||||
VersionSetting cloned = new VersionSetting();
|
||||
PropertyUtils.copyProperties(this, cloned);
|
||||
return cloned;
|
||||
}
|
||||
|
||||
public static class Serializer implements JsonSerializer<VersionSetting>, JsonDeserializer<VersionSetting> {
|
||||
|
||||
@@ -19,6 +19,9 @@ package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXDialogLayout;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.WeakInvalidationListener;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
@@ -34,6 +37,7 @@ import javafx.scene.paint.Color;
|
||||
import javafx.stage.Screen;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
@@ -42,6 +46,7 @@ import org.jackhuang.hmcl.setting.*;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.account.AccountListPage;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorController;
|
||||
@@ -267,6 +272,24 @@ public final class Controllers {
|
||||
stage.initStyle(StageStyle.TRANSPARENT);
|
||||
stage.setScene(scene);
|
||||
|
||||
if (AnimationUtils.playWindowAnimation()) {
|
||||
Timeline timeline = new Timeline(
|
||||
new KeyFrame(Duration.millis(0),
|
||||
new KeyValue(decorator.getDecorator().opacityProperty(), 0, FXUtils.EASE),
|
||||
new KeyValue(decorator.getDecorator().scaleXProperty(), 0.8, FXUtils.EASE),
|
||||
new KeyValue(decorator.getDecorator().scaleYProperty(), 0.8, FXUtils.EASE),
|
||||
new KeyValue(decorator.getDecorator().scaleZProperty(), 0.8, FXUtils.EASE)
|
||||
),
|
||||
new KeyFrame(Duration.millis(600),
|
||||
new KeyValue(decorator.getDecorator().opacityProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(decorator.getDecorator().scaleXProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(decorator.getDecorator().scaleYProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(decorator.getDecorator().scaleZProperty(), 1, FXUtils.EASE)
|
||||
)
|
||||
);
|
||||
timeline.play();
|
||||
}
|
||||
|
||||
if (!Architecture.SYSTEM_ARCH.isX86() && globalConfig().getPlatformPromptVersion() < 1) {
|
||||
Runnable continueAction = () -> globalConfig().setPlatformPromptVersion(1);
|
||||
|
||||
|
||||
@@ -18,18 +18,23 @@
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.*;
|
||||
import com.twelvemonkeys.imageio.plugins.webp.WebPImageReaderSpi;
|
||||
import javafx.animation.*;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.WeakInvalidationListener;
|
||||
import javafx.beans.WeakListener;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.value.*;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.event.Event;
|
||||
import javafx.event.EventDispatcher;
|
||||
import javafx.event.EventType;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.*;
|
||||
@@ -44,6 +49,7 @@ import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Callback;
|
||||
import javafx.util.Duration;
|
||||
@@ -53,11 +59,9 @@ import org.glavo.png.PNGWriter;
|
||||
import org.glavo.png.javafx.PNGJavaFXUtils;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
import org.jackhuang.hmcl.util.Holder;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.ResourceNotFoundError;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.ExtendedProperties;
|
||||
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
@@ -68,13 +72,15 @@ import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import java.io.*;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@@ -84,6 +90,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -113,6 +120,10 @@ public final class FXUtils {
|
||||
|
||||
public static final String DEFAULT_MONOSPACE_FONT = OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? "Consolas" : "Monospace";
|
||||
|
||||
public static final List<String> IMAGE_EXTENSIONS = Lang.immutableListOf(
|
||||
"png", "jpg", "jpeg", "bmp", "gif", "webp"
|
||||
);
|
||||
|
||||
private static final Map<String, Image> builtinImageCache = new ConcurrentHashMap<>();
|
||||
private static final Map<String, Path> remoteImageCache = new ConcurrentHashMap<>();
|
||||
|
||||
@@ -228,20 +239,21 @@ public final class FXUtils {
|
||||
});
|
||||
}
|
||||
|
||||
public static <K, T> void setupCellValueFactory(JFXTreeTableColumn<K, T> column, Function<K, ObservableValue<T>> mapper) {
|
||||
column.setCellValueFactory(param -> {
|
||||
if (column.validateValue(param))
|
||||
return mapper.apply(param.getValue().getValue());
|
||||
else
|
||||
return column.getComputedValue(param);
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends Event> void ignoreEvent(Node node, EventType<T> type, Predicate<? super T> filter) {
|
||||
EventDispatcher oldDispatcher = node.getEventDispatcher();
|
||||
node.setEventDispatcher((event, tail) -> {
|
||||
EventType<?> t = event.getEventType();
|
||||
while (t != null && t != type)
|
||||
t = t.getSuperType();
|
||||
if (t == type && filter.test((T) event)) {
|
||||
return tail.dispatchEvent(event);
|
||||
} else {
|
||||
return oldDispatcher.dispatchEvent(event, tail);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static Node wrapMargin(Node node, Insets insets) {
|
||||
StackPane.setMargin(node, insets);
|
||||
return new StackPane(node);
|
||||
}
|
||||
|
||||
public static void setValidateWhileTextChanged(Node field, boolean validate) {
|
||||
if (field instanceof JFXTextField) {
|
||||
if (validate) {
|
||||
@@ -515,10 +527,12 @@ public final class FXUtils {
|
||||
}
|
||||
|
||||
public static <T> void bind(JFXTextField textField, Property<T> property, StringConverter<T> converter) {
|
||||
textField.setText(converter == null ? (String) property.getValue() : converter.toString(property.getValue()));
|
||||
TextFieldBindingListener<T> listener = new TextFieldBindingListener<>(textField, property, converter);
|
||||
textField.focusedProperty().addListener((ChangeListener<Boolean>) listener);
|
||||
property.addListener(listener);
|
||||
TextFieldBinding<T> binding = new TextFieldBinding<>(textField, property, converter);
|
||||
binding.updateTextField();
|
||||
textField.getProperties().put("FXUtils.bind.binding", binding);
|
||||
textField.focusedProperty().addListener(binding.focusedListener);
|
||||
textField.sceneProperty().addListener(binding.sceneListener);
|
||||
property.addListener(binding.propertyListener);
|
||||
}
|
||||
|
||||
public static void bindInt(JFXTextField textField, Property<Number> property) {
|
||||
@@ -530,53 +544,116 @@ public final class FXUtils {
|
||||
}
|
||||
|
||||
public static void unbind(JFXTextField textField, Property<?> property) {
|
||||
TextFieldBindingListener<?> listener = new TextFieldBindingListener<>(textField, property, null);
|
||||
textField.focusedProperty().removeListener((ChangeListener<Boolean>) listener);
|
||||
property.removeListener(listener);
|
||||
TextFieldBinding<?> binding = (TextFieldBinding<?>) textField.getProperties().remove("FXUtils.bind.binding");
|
||||
if (binding != null) {
|
||||
textField.focusedProperty().removeListener(binding.focusedListener);
|
||||
textField.sceneProperty().removeListener(binding.sceneListener);
|
||||
property.removeListener(binding.propertyListener);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class TextFieldBindingListener<T> implements ChangeListener<Boolean>, InvalidationListener {
|
||||
private final int hashCode;
|
||||
private final WeakReference<JFXTextField> textFieldRef;
|
||||
private final WeakReference<Property<T>> propertyRef;
|
||||
private static final class TextFieldBinding<T> {
|
||||
private final JFXTextField textField;
|
||||
private final Property<T> property;
|
||||
private final StringConverter<T> converter;
|
||||
|
||||
TextFieldBindingListener(JFXTextField textField, Property<T> property, StringConverter<T> converter) {
|
||||
this.textFieldRef = new WeakReference<>(textField);
|
||||
this.propertyRef = new WeakReference<>(property);
|
||||
public final ChangeListener<Boolean> focusedListener;
|
||||
public final ChangeListener<Scene> sceneListener;
|
||||
public final InvalidationListener propertyListener;
|
||||
|
||||
public TextFieldBinding(JFXTextField textField, Property<T> property, StringConverter<T> converter) {
|
||||
this.textField = textField;
|
||||
this.property = property;
|
||||
this.converter = converter;
|
||||
this.hashCode = System.identityHashCode(textField) ^ System.identityHashCode(property);
|
||||
|
||||
focusedListener = (observable, oldFocused, newFocused) -> {
|
||||
if (oldFocused && !newFocused) {
|
||||
if (textField.validate()) {
|
||||
updateProperty();
|
||||
} else {
|
||||
// Rollback to old value
|
||||
updateTextField();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
sceneListener = (observable, oldScene, newScene) -> {
|
||||
if (oldScene != null && newScene == null) {
|
||||
// Component is being removed from scene
|
||||
if (textField.validate()) {
|
||||
updateProperty();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
propertyListener = observable -> {
|
||||
updateTextField();
|
||||
};
|
||||
}
|
||||
|
||||
public void updateProperty() {
|
||||
String newText = textField.getText();
|
||||
@SuppressWarnings("unchecked")
|
||||
T newValue = converter == null ? (T) newText : converter.fromString(newText);
|
||||
|
||||
if (!Objects.equals(newValue, property.getValue())) {
|
||||
property.setValue(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateTextField() {
|
||||
T value = property.getValue();
|
||||
textField.setText(converter == null ? (String) value : converter.toString(value));
|
||||
}
|
||||
}
|
||||
|
||||
private static final class EnumBidirectionalBinding<E extends Enum<E>> implements InvalidationListener, WeakListener {
|
||||
private final WeakReference<JFXComboBox<E>> comboBoxRef;
|
||||
private final WeakReference<Property<E>> propertyRef;
|
||||
private final int hashCode;
|
||||
|
||||
private boolean updating = false;
|
||||
|
||||
private EnumBidirectionalBinding(JFXComboBox<E> comboBox, Property<E> property) {
|
||||
this.comboBoxRef = new WeakReference<>(comboBox);
|
||||
this.propertyRef = new WeakReference<>(property);
|
||||
this.hashCode = System.identityHashCode(comboBox) ^ System.identityHashCode(property);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean focused) { // On TextField changed
|
||||
JFXTextField textField = textFieldRef.get();
|
||||
Property<T> property = this.propertyRef.get();
|
||||
public void invalidated(Observable sourceProperty) {
|
||||
if (!updating) {
|
||||
final JFXComboBox<E> comboBox = comboBoxRef.get();
|
||||
final Property<E> property = propertyRef.get();
|
||||
|
||||
if (textField != null && property != null && oldValue == Boolean.TRUE && focused == Boolean.FALSE) {
|
||||
if (textField.validate()) {
|
||||
String newText = textField.getText();
|
||||
@SuppressWarnings("unchecked")
|
||||
T newValue = converter == null ? (T) newText : converter.fromString(newText);
|
||||
if (comboBox == null || property == null) {
|
||||
if (comboBox != null) {
|
||||
comboBox.getSelectionModel().selectedItemProperty().removeListener(this);
|
||||
}
|
||||
|
||||
if (!Objects.equals(newValue, property.getValue()))
|
||||
property.setValue(newValue);
|
||||
if (property != null) {
|
||||
property.removeListener(this);
|
||||
}
|
||||
} else {
|
||||
// Rollback to old value
|
||||
invalidated(null);
|
||||
updating = true;
|
||||
try {
|
||||
if (property == sourceProperty) {
|
||||
E newValue = property.getValue();
|
||||
comboBox.getSelectionModel().select(newValue);
|
||||
} else {
|
||||
E newValue = comboBox.getSelectionModel().getSelectedItem();
|
||||
property.setValue(newValue);
|
||||
}
|
||||
} finally {
|
||||
updating = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidated(Observable observable) { // On property change
|
||||
JFXTextField textField = textFieldRef.get();
|
||||
Property<T> property = this.propertyRef.get();
|
||||
|
||||
if (textField != null && property != null) {
|
||||
T value = property.getValue();
|
||||
textField.setText(converter == null ? (String) value : converter.toString(value));
|
||||
}
|
||||
public boolean wasGarbageCollected() {
|
||||
return comboBoxRef.get() == null || propertyRef.get() == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -585,52 +662,45 @@ public final class FXUtils {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof TextFieldBindingListener))
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (!(o instanceof EnumBidirectionalBinding))
|
||||
return false;
|
||||
TextFieldBindingListener<?> other = (TextFieldBindingListener<?>) obj;
|
||||
return this.hashCode == other.hashCode
|
||||
&& this.textFieldRef.get() == other.textFieldRef.get()
|
||||
&& this.propertyRef.get() == other.propertyRef.get();
|
||||
|
||||
EnumBidirectionalBinding<?> that = (EnumBidirectionalBinding<?>) o;
|
||||
|
||||
final JFXComboBox<E> comboBox = this.comboBoxRef.get();
|
||||
final Property<E> property = this.propertyRef.get();
|
||||
|
||||
final JFXComboBox<?> thatComboBox = that.comboBoxRef.get();
|
||||
final Property<?> thatProperty = that.propertyRef.get();
|
||||
|
||||
if (comboBox == null || property == null || thatComboBox == null || thatProperty == null)
|
||||
return false;
|
||||
|
||||
return comboBox == thatComboBox && property == thatProperty;
|
||||
}
|
||||
}
|
||||
|
||||
public static void bindBoolean(JFXToggleButton toggleButton, Property<Boolean> property) {
|
||||
toggleButton.selectedProperty().bindBidirectional(property);
|
||||
}
|
||||
|
||||
public static void unbindBoolean(JFXToggleButton toggleButton, Property<Boolean> property) {
|
||||
toggleButton.selectedProperty().unbindBidirectional(property);
|
||||
}
|
||||
|
||||
public static void bindBoolean(JFXCheckBox checkBox, Property<Boolean> property) {
|
||||
checkBox.selectedProperty().bindBidirectional(property);
|
||||
}
|
||||
|
||||
public static void unbindBoolean(JFXCheckBox checkBox, Property<Boolean> property) {
|
||||
checkBox.selectedProperty().unbindBidirectional(property);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind combo box selection with given enum property bidirectionally.
|
||||
* You should <b>only and always</b> use {@code bindEnum} as well as {@code unbindEnum} at the same time.
|
||||
*
|
||||
* @param comboBox the combo box being bound with {@code property}.
|
||||
* @param property the property being bound with {@code combo box}.
|
||||
* @see #unbindEnum(JFXComboBox)
|
||||
* @see #unbindEnum(JFXComboBox, Property)
|
||||
* @see ExtendedProperties#selectedItemPropertyFor(ComboBox)
|
||||
*/
|
||||
public static <T extends Enum<T>> void bindEnum(JFXComboBox<T> comboBox, Property<T> property) {
|
||||
unbindEnum(comboBox);
|
||||
EnumBidirectionalBinding<T> binding = new EnumBidirectionalBinding<>(comboBox, property);
|
||||
|
||||
T currentValue = property.getValue();
|
||||
@SuppressWarnings("unchecked")
|
||||
T[] enumConstants = (T[]) currentValue.getClass().getEnumConstants();
|
||||
ChangeListener<Number> listener = (a, b, newValue) -> property.setValue(enumConstants[newValue.intValue()]);
|
||||
comboBox.getSelectionModel().selectedItemProperty().removeListener(binding);
|
||||
property.removeListener(binding);
|
||||
|
||||
comboBox.getSelectionModel().select(currentValue.ordinal());
|
||||
comboBox.getProperties().put("FXUtils.bindEnum.listener", listener);
|
||||
comboBox.getSelectionModel().selectedIndexProperty().addListener(listener);
|
||||
comboBox.getSelectionModel().select(property.getValue());
|
||||
comboBox.getSelectionModel().selectedItemProperty().addListener(binding);
|
||||
property.addListener(binding);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -641,11 +711,10 @@ public final class FXUtils {
|
||||
* @see #bindEnum(JFXComboBox, Property)
|
||||
* @see ExtendedProperties#selectedItemPropertyFor(ComboBox)
|
||||
*/
|
||||
public static void unbindEnum(JFXComboBox<? extends Enum<?>> comboBox) {
|
||||
@SuppressWarnings("unchecked")
|
||||
ChangeListener<Number> listener = (ChangeListener<Number>) comboBox.getProperties().remove("FXUtils.bindEnum.listener");
|
||||
if (listener != null)
|
||||
comboBox.getSelectionModel().selectedIndexProperty().removeListener(listener);
|
||||
public static <T extends Enum<T>> void unbindEnum(JFXComboBox<T> comboBox, Property<T> property) {
|
||||
EnumBidirectionalBinding<T> binding = new EnumBidirectionalBinding<>(comboBox, property);
|
||||
comboBox.getSelectionModel().selectedItemProperty().removeListener(binding);
|
||||
property.removeListener(binding);
|
||||
}
|
||||
|
||||
public static void bindAllEnabled(BooleanProperty allEnabled, BooleanProperty... children) {
|
||||
@@ -713,6 +782,50 @@ public final class FXUtils {
|
||||
stage.getIcons().add(newBuiltinImage(icon));
|
||||
}
|
||||
|
||||
private static Image loadWebPImage(InputStream input) throws IOException {
|
||||
WebPImageReaderSpi spi = new WebPImageReaderSpi();
|
||||
ImageReader reader = spi.createReaderInstance(null);
|
||||
|
||||
try (ImageInputStream imageInput = ImageIO.createImageInputStream(input)) {
|
||||
reader.setInput(imageInput, true, true);
|
||||
return SwingFXUtils.toFXImage(reader.read(0, reader.getDefaultReadParam()), null);
|
||||
} finally {
|
||||
reader.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public static Image loadImage(Path path) throws Exception {
|
||||
try (InputStream input = Files.newInputStream(path)) {
|
||||
if ("webp".equalsIgnoreCase(FileUtils.getExtension(path)))
|
||||
return loadWebPImage(input);
|
||||
else {
|
||||
Image image = new Image(input);
|
||||
if (image.isError())
|
||||
throw image.getException();
|
||||
return image;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Image loadImage(URL url) throws Exception {
|
||||
URLConnection connection = NetworkUtils.createConnection(url);
|
||||
if (connection instanceof HttpURLConnection) {
|
||||
connection = NetworkUtils.resolveConnection((HttpURLConnection) connection);
|
||||
}
|
||||
|
||||
try (InputStream input = connection.getInputStream()) {
|
||||
String path = url.getPath();
|
||||
if (path != null && "webp".equalsIgnoreCase(StringUtils.substringAfterLast(path, '.')))
|
||||
return loadWebPImage(input);
|
||||
else {
|
||||
Image image = new Image(input);
|
||||
if (image.isError())
|
||||
throw image.getException();
|
||||
return image;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Suppress IllegalArgumentException since the url is supposed to be correct definitely.
|
||||
*
|
||||
@@ -941,6 +1054,8 @@ public final class FXUtils {
|
||||
}
|
||||
};
|
||||
|
||||
public static final Interpolator EASE = Interpolator.SPLINE(0.25, 0.1, 0.25, 1);
|
||||
|
||||
public static void onEscPressed(Node node, Runnable action) {
|
||||
node.addEventHandler(KeyEvent.KEY_PRESSED, e -> {
|
||||
if (e.getCode() == KeyCode.ESCAPE) {
|
||||
@@ -959,6 +1074,18 @@ public final class FXUtils {
|
||||
});
|
||||
}
|
||||
|
||||
public static void copyOnDoubleClick(Labeled label) {
|
||||
label.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> {
|
||||
if (e.getButton() == MouseButton.PRIMARY && e.getClickCount() == 2) {
|
||||
String text = label.getText();
|
||||
if (text != null && !text.isEmpty()) {
|
||||
copyText(label.getText());
|
||||
e.consume();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void copyText(String text) {
|
||||
ClipboardContent content = new ClipboardContent();
|
||||
content.putString(text);
|
||||
@@ -1034,4 +1161,9 @@ public final class FXUtils {
|
||||
|
||||
return String.format("#%02x%02x%02x", r, g, b);
|
||||
}
|
||||
|
||||
public static FileChooser.ExtensionFilter getImageExtensionFilter() {
|
||||
return new FileChooser.ExtensionFilter(i18n("extension.png"),
|
||||
IMAGE_EXTENSIONS.stream().map(ext -> "*." + ext).toArray(String[]::new));
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,7 @@ public enum SVG {
|
||||
ALERT_OUTLINE("M12,2L1,21H23M12,6L19.53,19H4.47M11,10V14H13V10M11,16V18H13V16"),
|
||||
PLUS("M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z"),
|
||||
PLUS_CIRCLE_OUTLINE("M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M13,7H11V11H7V13H11V17H13V13H17V11H13V7Z"),
|
||||
MINUS("M19 13H5V11H19V13Z"),
|
||||
IMPORT_ICON("M14,12L10,8V11H2V13H10V16M20,18V6C20,4.89 19.1,4 18,4H6A2,2 0 0,0 4,6V9H6V6H18V18H6V15H4V18A2,2 0 0,0 6,20H18A2,2 0 0,0 20,18Z"),
|
||||
EXPORT("M23,12L19,8V11H10V13H19V16M1,18V6C1,4.89 1.9,4 3,4H15A2,2 0 0,1 17,6V9H15V6H3V18H15V15H17V18A2,2 0 0,1 15,20H3A2,2 0 0,1 1,18Z"),
|
||||
OPEN_IN_NEW("M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z"),
|
||||
|
||||
@@ -41,6 +41,9 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class UpgradeDialog extends JFXDialogLayout {
|
||||
public UpgradeDialog(RemoteVersion remoteVersion, Runnable updateRunnable) {
|
||||
maxWidthProperty().bind(Controllers.getScene().widthProperty().multiply(0.7));
|
||||
maxHeightProperty().bind(Controllers.getScene().heightProperty().multiply(0.7));
|
||||
|
||||
setHeading(new Label(i18n("update.changelog")));
|
||||
setBody(new ProgressIndicator());
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXRadioButton;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.canvas.Canvas;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.SkinBase;
|
||||
@@ -50,13 +51,11 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
super(skinnable);
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
root.setCursor(Cursor.HAND);
|
||||
FXUtils.onClicked(root, skinnable::fire);
|
||||
|
||||
JFXRadioButton chkSelected = new JFXRadioButton() {
|
||||
@Override
|
||||
public void fire() {
|
||||
skinnable.fire();
|
||||
}
|
||||
};
|
||||
JFXRadioButton chkSelected = new JFXRadioButton();
|
||||
chkSelected.setMouseTransparent(true);
|
||||
BorderPane.setAlignment(chkSelected, Pos.CENTER);
|
||||
chkSelected.selectedProperty().bind(skinnable.selectedProperty());
|
||||
root.setLeft(chkSelected);
|
||||
|
||||
@@ -101,6 +101,9 @@ public class OfflineAccountSkinPane extends StackPane {
|
||||
VBox optionPane = new VBox(skinItem, skinOptionPane);
|
||||
pane.setRight(optionPane);
|
||||
|
||||
skinSelector.maxWidthProperty().bind(skinOptionPane.maxWidthProperty().multiply(0.7));
|
||||
capeSelector.maxWidthProperty().bind(skinOptionPane.maxWidthProperty().multiply(0.7));
|
||||
|
||||
layout.setBody(pane);
|
||||
|
||||
cslApiField.setPromptText(i18n("account.skin.type.csl_api.location.hint"));
|
||||
@@ -108,10 +111,10 @@ public class OfflineAccountSkinPane extends StackPane {
|
||||
|
||||
skinItem.loadChildren(Arrays.asList(
|
||||
new MultiFileItem.Option<>(i18n("message.default"), Skin.Type.DEFAULT),
|
||||
new MultiFileItem.Option<>("Steve", Skin.Type.STEVE),
|
||||
new MultiFileItem.Option<>("Alex", Skin.Type.ALEX),
|
||||
new MultiFileItem.Option<>(i18n("account.skin.type.steve"), Skin.Type.STEVE),
|
||||
new MultiFileItem.Option<>(i18n("account.skin.type.alex"), Skin.Type.ALEX),
|
||||
new MultiFileItem.Option<>(i18n("account.skin.type.local_file"), Skin.Type.LOCAL_FILE),
|
||||
new MultiFileItem.Option<>("LittleSkin", Skin.Type.LITTLE_SKIN),
|
||||
new MultiFileItem.Option<>(i18n("account.skin.type.little_skin"), Skin.Type.LITTLE_SKIN),
|
||||
new MultiFileItem.Option<>(i18n("account.skin.type.csl_api"), Skin.Type.CUSTOM_SKIN_LOADER_API)
|
||||
));
|
||||
|
||||
@@ -201,6 +204,12 @@ public class OfflineAccountSkinPane extends StackPane {
|
||||
}
|
||||
|
||||
private Skin getSkin() {
|
||||
return new Skin(skinItem.getSelectedData(), cslApiField.getText(), modelCombobox.getValue(), skinSelector.getValue(), capeSelector.getValue());
|
||||
Skin.Type type = skinItem.getSelectedData();
|
||||
if (type == Skin.Type.LOCAL_FILE) {
|
||||
return new Skin(type, cslApiField.getText(), modelCombobox.getValue(), skinSelector.getValue(), capeSelector.getValue());
|
||||
} else {
|
||||
String cslApi = type == Skin.Type.CUSTOM_SKIN_LOADER_API ? cslApiField.getText() : null;
|
||||
return new Skin(type, cslApi, null, null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,28 @@
|
||||
/*
|
||||
* 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.animation;
|
||||
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class AnimationUtils {
|
||||
|
||||
private AnimationUtils() {
|
||||
@@ -15,9 +36,15 @@ public final class AnimationUtils {
|
||||
public static void init() {
|
||||
}
|
||||
|
||||
private static final boolean enabled = !ConfigHolder.config().isAnimationDisabled();
|
||||
private static final boolean ENABLED = !ConfigHolder.config().isAnimationDisabled();
|
||||
private static final boolean PLAY_WINDOW_ANIMATION = ENABLED
|
||||
&& (!OperatingSystem.CURRENT_OS.isLinuxOrBSD() || "DDE".equals(System.getenv("XDG_CURRENT_DESKTOP")));
|
||||
|
||||
public static boolean isAnimationEnabled() {
|
||||
return enabled;
|
||||
return ENABLED;
|
||||
}
|
||||
|
||||
public static boolean playWindowAnimation() {
|
||||
return PLAY_WINDOW_ANIMATION;
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.animation;
|
||||
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.Node;
|
||||
@@ -26,6 +25,8 @@ import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
|
||||
public class TransitionPane extends StackPane implements AnimationHandler {
|
||||
private static final Duration DEFAULT_DURATION = Duration.millis(200);
|
||||
|
||||
private Duration duration;
|
||||
private Node previousNode, currentNode;
|
||||
|
||||
@@ -55,7 +56,7 @@ public class TransitionPane extends StackPane implements AnimationHandler {
|
||||
}
|
||||
|
||||
public void setContent(Node newView, AnimationProducer transition) {
|
||||
setContent(newView, transition, Duration.millis(160));
|
||||
setContent(newView, transition, DEFAULT_DURATION);
|
||||
}
|
||||
|
||||
public void setContent(Node newView, AnimationProducer transition, Duration duration) {
|
||||
@@ -64,26 +65,25 @@ public class TransitionPane extends StackPane implements AnimationHandler {
|
||||
updateContent(newView);
|
||||
|
||||
if (previousNode == EMPTY_PANE) {
|
||||
setMouseTransparent(false);
|
||||
getChildren().setAll(newView);
|
||||
return;
|
||||
}
|
||||
|
||||
if (AnimationUtils.isAnimationEnabled()) {
|
||||
if (AnimationUtils.isAnimationEnabled() && transition != ContainerAnimations.NONE) {
|
||||
setMouseTransparent(true);
|
||||
transition.init(this);
|
||||
|
||||
// runLater or "init" will not work
|
||||
Platform.runLater(() -> {
|
||||
Timeline newAnimation = new Timeline();
|
||||
newAnimation.getKeyFrames().addAll(transition.animate(this));
|
||||
newAnimation.getKeyFrames().add(new KeyFrame(duration, e -> {
|
||||
newAnimation.getKeyFrames().setAll(transition.animate(this));
|
||||
newAnimation.setOnFinished(e -> {
|
||||
setMouseTransparent(false);
|
||||
getChildren().remove(previousNode);
|
||||
}));
|
||||
});
|
||||
FXUtils.playAnimation(this, "transition_pane", newAnimation);
|
||||
});
|
||||
} else {
|
||||
setMouseTransparent(false);
|
||||
getChildren().remove(previousNode);
|
||||
}
|
||||
}
|
||||
@@ -99,8 +99,6 @@ public class TransitionPane extends StackPane implements AnimationHandler {
|
||||
if (previousNode == newView)
|
||||
previousNode = EMPTY_PANE;
|
||||
|
||||
setMouseTransparent(true);
|
||||
|
||||
currentNode = newView;
|
||||
|
||||
getChildren().setAll(previousNode, currentNode);
|
||||
|
||||
@@ -24,6 +24,8 @@ import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.versions.VersionPage;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -54,11 +56,36 @@ public class AdvancedListBox extends ScrollPane {
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdvancedListBox addNavigationDrawerItem(Consumer<AdvancedListItem> fn) {
|
||||
private AdvancedListItem createNavigationDrawerItem(String title, SVG leftGraphic) {
|
||||
AdvancedListItem item = new AdvancedListItem();
|
||||
item.getStyleClass().add("navigation-drawer-item");
|
||||
item.setActionButtonVisible(false);
|
||||
fn.accept(item);
|
||||
item.setTitle(title);
|
||||
if (leftGraphic != null) {
|
||||
item.setLeftGraphic(VersionPage.wrap(leftGraphic));
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
public AdvancedListBox addNavigationDrawerItem(String title, SVG leftGraphic, Runnable onAction) {
|
||||
return addNavigationDrawerItem(title, leftGraphic, onAction, null);
|
||||
}
|
||||
|
||||
public AdvancedListBox addNavigationDrawerItem(String title, SVG leftGraphic, Runnable onAction, Consumer<AdvancedListItem> initializer) {
|
||||
AdvancedListItem item = createNavigationDrawerItem(title, leftGraphic);
|
||||
if (onAction != null) {
|
||||
item.setOnAction(e -> onAction.run());
|
||||
}
|
||||
if (initializer != null) {
|
||||
initializer.accept(item);
|
||||
}
|
||||
return add(item);
|
||||
}
|
||||
|
||||
public AdvancedListBox addNavigationDrawerTab(TabHeader tabHeader, TabControl.Tab<?> tab, String title, SVG leftGraphic) {
|
||||
AdvancedListItem item = createNavigationDrawerItem(title, leftGraphic);
|
||||
item.activeProperty().bind(tabHeader.getSelectionModel().selectedItemProperty().isEqualTo(tab));
|
||||
item.setOnAction(e -> tabHeader.select(tab));
|
||||
return add(item);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
|
||||
public final class AnnouncementCard extends VBox {
|
||||
|
||||
public AnnouncementCard(String title, String content, Runnable onClose) {
|
||||
TextFlow body = FXUtils.segmentToTextFlow(content, Controllers::onHyperlinkAction);
|
||||
body.setLineSpacing(4);
|
||||
|
||||
BorderPane titleBar = new BorderPane();
|
||||
titleBar.getStyleClass().add("title");
|
||||
titleBar.setLeft(new Label(title));
|
||||
|
||||
if (onClose != null) {
|
||||
Node hideNode = SVG.CLOSE.createIcon(Theme.blackFill(), 20, 20);
|
||||
hideNode.setOnMouseClicked(e -> onClose.run());
|
||||
hideNode.setCursor(Cursor.HAND);
|
||||
titleBar.setRight(hideNode);
|
||||
}
|
||||
|
||||
getChildren().setAll(titleBar, body);
|
||||
setSpacing(16);
|
||||
getStyleClass().addAll("card", "announcement");
|
||||
}
|
||||
}
|
||||
@@ -149,8 +149,8 @@ final class ComponentListCell extends StackPane {
|
||||
}
|
||||
|
||||
Platform.runLater(() -> {
|
||||
double newAnimatedHeight = (list.prefHeight(-1) + 8 + 10) * (expanded ? 1 : -1);
|
||||
double newHeight = expanded ? getHeight() + newAnimatedHeight : prefHeight(-1);
|
||||
double newAnimatedHeight = (list.prefHeight(list.getWidth()) + 8 + 10) * (expanded ? 1 : -1);
|
||||
double newHeight = expanded ? getHeight() + newAnimatedHeight : prefHeight(list.getWidth());
|
||||
double contentHeight = expanded ? newAnimatedHeight : 0;
|
||||
|
||||
if (expanded) {
|
||||
|
||||
@@ -17,7 +17,6 @@ import javafx.scene.layout.Priority;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.util.Lazy;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
public class IconedTwoLineListItem extends HBox {
|
||||
@@ -29,13 +28,8 @@ public class IconedTwoLineListItem extends HBox {
|
||||
|
||||
private final ImageView imageView = new ImageView();
|
||||
private final TwoLineListItem twoLineListItem = new TwoLineListItem();
|
||||
private final Lazy<JFXButton> externalLinkButton = new Lazy<>(() -> {
|
||||
JFXButton button = new JFXButton();
|
||||
button.getStyleClass().add("toggle-icon4");
|
||||
button.setGraphic(SVG.OPEN_IN_NEW.createIcon(Theme.blackFill(), -1, -1));
|
||||
button.setOnAction(e -> FXUtils.openLink(externalLink.get()));
|
||||
return button;
|
||||
});
|
||||
private JFXButton externalLinkButton;
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private final InvalidationListener observer;
|
||||
|
||||
@@ -52,7 +46,7 @@ public class IconedTwoLineListItem extends HBox {
|
||||
getChildren().clear();
|
||||
if (image.get() != null) getChildren().add(imageView);
|
||||
getChildren().add(twoLineListItem);
|
||||
if (StringUtils.isNotBlank(externalLink.get())) getChildren().add(externalLinkButton.get());
|
||||
if (StringUtils.isNotBlank(externalLink.get())) getChildren().add(getExternalLinkButton());
|
||||
}, image, externalLink);
|
||||
}
|
||||
|
||||
@@ -111,4 +105,14 @@ public class IconedTwoLineListItem extends HBox {
|
||||
public ImageView getImageView() {
|
||||
return imageView;
|
||||
}
|
||||
|
||||
public JFXButton getExternalLinkButton() {
|
||||
if (externalLinkButton == null) {
|
||||
externalLinkButton = new JFXButton();
|
||||
externalLinkButton.getStyleClass().add("toggle-icon4");
|
||||
externalLinkButton.setGraphic(SVG.OPEN_IN_NEW.createIcon(Theme.blackFill(), -1, -1));
|
||||
externalLinkButton.setOnAction(e -> FXUtils.openLink(externalLink.get()));
|
||||
}
|
||||
return externalLinkButton;
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import org.jackhuang.hmcl.util.FutureCallback;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class InputDialogPane extends JFXDialogLayout {
|
||||
@@ -68,6 +69,8 @@ public class InputDialogPane extends JFXDialogLayout {
|
||||
lblCreationWarning.setText(msg);
|
||||
});
|
||||
});
|
||||
|
||||
onEscPressed(this, cancelButton::fire);
|
||||
}
|
||||
|
||||
public CompletableFuture<String> getCompletableFuture() {
|
||||
|
||||
+10
-6
@@ -20,24 +20,28 @@ package org.jackhuang.hmcl.ui.construct;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Optional;
|
||||
import java.util.Stack;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public class StackContainerPane extends StackPane {
|
||||
private final Stack<Node> stack = new Stack<>();
|
||||
public class JFXDialogPane extends StackPane {
|
||||
private final ArrayList<Node> stack = new ArrayList<>();
|
||||
|
||||
public int size() {
|
||||
return stack.size();
|
||||
}
|
||||
|
||||
public Optional<Node> peek() {
|
||||
if (stack.isEmpty()) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return Optional.of(stack.peek());
|
||||
return Optional.of(stack.get(stack.size() - 1));
|
||||
}
|
||||
}
|
||||
|
||||
public void push(Node node) {
|
||||
stack.push(node);
|
||||
stack.add(node);
|
||||
getChildren().setAll(node);
|
||||
|
||||
LOG.info(this + " " + stack);
|
||||
@@ -48,7 +52,7 @@ public class StackContainerPane extends StackPane {
|
||||
if (stack.isEmpty())
|
||||
getChildren().setAll();
|
||||
else
|
||||
getChildren().setAll(stack.peek());
|
||||
getChildren().setAll(stack.get(stack.size() - 1));
|
||||
|
||||
LOG.info(this + " " + stack + ", removed: " + flag + ", object: " + node);
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import javafx.beans.binding.DoubleBinding;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.layout.Region;
|
||||
@@ -45,9 +46,11 @@ public abstract class MDListCell<T> extends ListCell<T> {
|
||||
Region clippedContainer = (Region) listView.lookup(".clipped-container");
|
||||
setPrefWidth(0);
|
||||
if (clippedContainer != null) {
|
||||
maxWidthProperty().bind(clippedContainer.widthProperty());
|
||||
prefWidthProperty().bind(clippedContainer.widthProperty());
|
||||
minWidthProperty().bind(clippedContainer.widthProperty());
|
||||
DoubleBinding converted = clippedContainer.widthProperty().subtract(1);
|
||||
|
||||
maxWidthProperty().bind(converted);
|
||||
prefWidthProperty().bind(converted);
|
||||
minWidthProperty().bind(converted);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ import com.jfoenix.controls.JFXRadioButton;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import com.jfoenix.validation.base.ValidatorBase;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
@@ -114,6 +113,7 @@ public final class MultiFileItem<T> extends VBox {
|
||||
public static class Option<T> {
|
||||
protected final String title;
|
||||
protected String subtitle;
|
||||
protected String tooltip;
|
||||
protected final T data;
|
||||
protected final BooleanProperty selected = new SimpleBooleanProperty();
|
||||
protected final JFXRadioButton left = new JFXRadioButton();
|
||||
@@ -140,6 +140,11 @@ public final class MultiFileItem<T> extends VBox {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Option<T> setTooltip(String tooltip) {
|
||||
this.tooltip = tooltip;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isSelected() {
|
||||
return left.isSelected();
|
||||
}
|
||||
@@ -161,6 +166,8 @@ public final class MultiFileItem<T> extends VBox {
|
||||
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
|
||||
left.setToggleGroup(group);
|
||||
left.setUserData(data);
|
||||
if (StringUtils.isNotBlank(tooltip))
|
||||
FXUtils.installFastTooltip(left, tooltip);
|
||||
pane.setLeft(left);
|
||||
|
||||
if (StringUtils.isNotBlank(subtitle)) {
|
||||
@@ -268,8 +275,9 @@ public final class MultiFileItem<T> extends VBox {
|
||||
return this;
|
||||
}
|
||||
|
||||
public ObservableList<FileChooser.ExtensionFilter> getExtensionFilters() {
|
||||
return selector.getExtensionFilters();
|
||||
public FileOption<T> addExtensionFilter(FileChooser.ExtensionFilter filter) {
|
||||
selector.getExtensionFilters().add(filter);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -39,6 +39,7 @@ import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import java.util.List;
|
||||
@@ -46,6 +47,7 @@ import java.util.List;
|
||||
@DefaultProperty("container")
|
||||
public class RipplerContainer extends StackPane {
|
||||
private static final String DEFAULT_STYLE_CLASS = "rippler-container";
|
||||
private static final Duration DURATION = Duration.millis(200);
|
||||
|
||||
private final ObjectProperty<Node> container = new SimpleObjectProperty<>(this, "container", null);
|
||||
private final StyleableObjectProperty<Paint> ripplerFill = new SimpleStyleableObjectProperty<>(StyleableProperties.RIPPLER_FILL,this, "ripplerFill", null);
|
||||
@@ -103,11 +105,10 @@ public class RipplerContainer extends StackPane {
|
||||
rectangle.heightProperty().bind(heightProperty());
|
||||
}));
|
||||
|
||||
setOnMouseEntered(e -> {
|
||||
new Transition() {
|
||||
if (AnimationUtils.isAnimationEnabled()) {
|
||||
setOnMouseEntered(e -> new Transition() {
|
||||
{
|
||||
setCycleCount(1);
|
||||
setCycleDuration(Duration.millis(200));
|
||||
setCycleDuration(DURATION);
|
||||
setInterpolator(Interpolator.EASE_IN);
|
||||
}
|
||||
|
||||
@@ -115,14 +116,11 @@ public class RipplerContainer extends StackPane {
|
||||
protected void interpolate(double frac) {
|
||||
interpolateBackground(frac);
|
||||
}
|
||||
}.play();
|
||||
});
|
||||
}.play());
|
||||
|
||||
setOnMouseExited(e -> {
|
||||
new Transition() {
|
||||
setOnMouseExited(e -> new Transition() {
|
||||
{
|
||||
setCycleCount(1);
|
||||
setCycleDuration(Duration.millis(200));
|
||||
setCycleDuration(DURATION);
|
||||
setInterpolator(Interpolator.EASE_OUT);
|
||||
}
|
||||
|
||||
@@ -130,8 +128,11 @@ public class RipplerContainer extends StackPane {
|
||||
protected void interpolate(double frac) {
|
||||
interpolateBackground(1 - frac);
|
||||
}
|
||||
}.play();
|
||||
});
|
||||
}.play());
|
||||
} else {
|
||||
setOnMouseEntered(e -> interpolateBackground(1));
|
||||
setOnMouseExited(e -> interpolateBackground(0));
|
||||
}
|
||||
}
|
||||
|
||||
private void interpolateBackground(double frac) {
|
||||
|
||||
@@ -157,23 +157,5 @@ public class SpinnerPane extends Control {
|
||||
}
|
||||
}
|
||||
|
||||
public interface State {}
|
||||
|
||||
public static class LoadedState implements State {}
|
||||
|
||||
public static class LoadingState implements State {}
|
||||
|
||||
public static class FailedState implements State {
|
||||
private final String reason;
|
||||
|
||||
public FailedState(String reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public String getReason() {
|
||||
return reason;
|
||||
}
|
||||
}
|
||||
|
||||
public static final EventType<Event> FAILED_ACTION = new EventType<>(Event.ANY, "FAILED_ACTION");
|
||||
}
|
||||
@@ -18,6 +18,9 @@
|
||||
package org.jackhuang.hmcl.ui.decorator;
|
||||
|
||||
import com.jfoenix.controls.JFXSnackbar;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
@@ -35,6 +38,9 @@ import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
import org.jackhuang.hmcl.ui.wizard.Navigation;
|
||||
|
||||
public class Decorator extends Control {
|
||||
@@ -61,12 +67,40 @@ public class Decorator extends Control {
|
||||
private final ReadOnlyBooleanWrapper allowMove = new ReadOnlyBooleanWrapper();
|
||||
private final ReadOnlyBooleanWrapper dragging = new ReadOnlyBooleanWrapper();
|
||||
|
||||
private boolean playRestoreMinimizeAnimation = false;
|
||||
|
||||
public Decorator(Stage primaryStage) {
|
||||
this.primaryStage = primaryStage;
|
||||
|
||||
setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
|
||||
primaryStage.initStyle(StageStyle.UNDECORATED);
|
||||
|
||||
if (AnimationUtils.playWindowAnimation()) {
|
||||
FXUtils.onChange(primaryStage.iconifiedProperty(), iconified -> {
|
||||
if (playRestoreMinimizeAnimation && !iconified) {
|
||||
playRestoreMinimizeAnimation = false;
|
||||
Timeline timeline = new Timeline(
|
||||
new KeyFrame(Duration.millis(0),
|
||||
new KeyValue(this.opacityProperty(), 0, FXUtils.EASE),
|
||||
new KeyValue(this.translateYProperty(), 200, FXUtils.EASE),
|
||||
new KeyValue(this.scaleXProperty(), 0.4, FXUtils.EASE),
|
||||
new KeyValue(this.scaleYProperty(), 0.4, FXUtils.EASE),
|
||||
new KeyValue(this.scaleZProperty(), 0.4, FXUtils.EASE)
|
||||
),
|
||||
new KeyFrame(Duration.millis(200),
|
||||
new KeyValue(this.opacityProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(this.translateYProperty(), 0, FXUtils.EASE),
|
||||
new KeyValue(this.scaleXProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(this.scaleYProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(this.scaleZProperty(), 1, FXUtils.EASE)
|
||||
)
|
||||
);
|
||||
timeline.play();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Stage getPrimaryStage() {
|
||||
@@ -239,7 +273,29 @@ public class Decorator extends Control {
|
||||
}
|
||||
|
||||
public void minimize() {
|
||||
primaryStage.setIconified(true);
|
||||
if (AnimationUtils.playWindowAnimation()) {
|
||||
playRestoreMinimizeAnimation = true;
|
||||
Timeline timeline = new Timeline(
|
||||
new KeyFrame(Duration.millis(0),
|
||||
new KeyValue(this.opacityProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(this.translateYProperty(), 0, FXUtils.EASE),
|
||||
new KeyValue(this.scaleXProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(this.scaleYProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(this.scaleZProperty(), 1, FXUtils.EASE)
|
||||
),
|
||||
new KeyFrame(Duration.millis(200),
|
||||
new KeyValue(this.opacityProperty(), 0, FXUtils.EASE),
|
||||
new KeyValue(this.translateYProperty(), 200, FXUtils.EASE),
|
||||
new KeyValue(this.scaleXProperty(), 0.4, FXUtils.EASE),
|
||||
new KeyValue(this.scaleYProperty(), 0.4, FXUtils.EASE),
|
||||
new KeyValue(this.scaleZProperty(), 0.4, FXUtils.EASE)
|
||||
)
|
||||
);
|
||||
timeline.setOnFinished(event -> primaryStage.setIconified(true));
|
||||
timeline.play();
|
||||
} else {
|
||||
primaryStage.setIconified(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
|
||||
@@ -31,7 +31,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class DecoratorAnimationProducer implements AnimationProducer {
|
||||
public final class DecoratorAnimationProducer implements AnimationProducer {
|
||||
@Override
|
||||
public void init(AnimationHandler handler) {
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@ package org.jackhuang.hmcl.ui.decorator;
|
||||
|
||||
import com.jfoenix.controls.JFXDialog;
|
||||
import com.jfoenix.controls.JFXSnackbar;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.WeakInvalidationListener;
|
||||
@@ -35,6 +38,7 @@ import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDnD;
|
||||
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
|
||||
@@ -42,20 +46,18 @@ import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.account.AddAuthlibInjectorServerPane;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogAware;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.Navigator;
|
||||
import org.jackhuang.hmcl.ui.construct.StackContainerPane;
|
||||
import org.jackhuang.hmcl.ui.construct.JFXDialogPane;
|
||||
import org.jackhuang.hmcl.ui.wizard.Refreshable;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@@ -80,11 +82,32 @@ public class DecoratorController {
|
||||
private final Navigator navigator;
|
||||
|
||||
private JFXDialog dialog;
|
||||
private StackContainerPane dialogPane;
|
||||
private JFXDialogPane dialogPane;
|
||||
|
||||
public DecoratorController(Stage stage, Node mainPage) {
|
||||
decorator = new Decorator(stage);
|
||||
decorator.setOnCloseButtonAction(Launcher::stopApplication);
|
||||
decorator.setOnCloseButtonAction(() -> {
|
||||
if (AnimationUtils.playWindowAnimation()) {
|
||||
Timeline timeline = new Timeline(
|
||||
new KeyFrame(Duration.millis(0),
|
||||
new KeyValue(decorator.opacityProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(decorator.scaleXProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(decorator.scaleYProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(decorator.scaleZProperty(), 0.3, FXUtils.EASE)
|
||||
),
|
||||
new KeyFrame(Duration.millis(200),
|
||||
new KeyValue(decorator.opacityProperty(), 0, FXUtils.EASE),
|
||||
new KeyValue(decorator.scaleXProperty(), 0.8, FXUtils.EASE),
|
||||
new KeyValue(decorator.scaleYProperty(), 0.8, FXUtils.EASE),
|
||||
new KeyValue(decorator.scaleZProperty(), 0.8, FXUtils.EASE)
|
||||
)
|
||||
);
|
||||
timeline.setOnFinished(event -> Launcher.stopApplication());
|
||||
timeline.play();
|
||||
} else {
|
||||
Launcher.stopApplication();
|
||||
}
|
||||
});
|
||||
decorator.titleTransparentProperty().bind(config().titleTransparentProperty());
|
||||
|
||||
navigator = new Navigator();
|
||||
@@ -180,23 +203,13 @@ public class DecoratorController {
|
||||
case CUSTOM:
|
||||
String backgroundImage = config().getBackgroundImage();
|
||||
if (backgroundImage != null)
|
||||
image = tryLoadImage(Paths.get(backgroundImage)).orElse(null);
|
||||
image = tryLoadImage(Paths.get(backgroundImage));
|
||||
break;
|
||||
case NETWORK:
|
||||
String backgroundImageUrl = config().getBackgroundImageUrl();
|
||||
if (backgroundImageUrl != null) {
|
||||
try {
|
||||
URLConnection connection = NetworkUtils.createConnection(new URL(backgroundImageUrl));
|
||||
if (connection instanceof HttpURLConnection) {
|
||||
connection = NetworkUtils.resolveConnection((HttpURLConnection) connection);
|
||||
}
|
||||
|
||||
try (InputStream input = connection.getInputStream()) {
|
||||
image = new Image(input);
|
||||
if (image.isError()) {
|
||||
throw image.getException();
|
||||
}
|
||||
}
|
||||
image = FXUtils.loadImage(new URL(backgroundImageUrl));
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Couldn't load background image", e);
|
||||
}
|
||||
@@ -218,73 +231,57 @@ public class DecoratorController {
|
||||
* Load background image from bg/, background.png, background.jpg, background.gif
|
||||
*/
|
||||
private Image loadDefaultBackgroundImage() {
|
||||
Optional<Image> image = randomImageIn(Paths.get("bg"));
|
||||
if (!image.isPresent()) {
|
||||
image = tryLoadImage(Paths.get("background.png"));
|
||||
Image image = randomImageIn(Paths.get("bg"));
|
||||
if (image != null)
|
||||
return image;
|
||||
|
||||
for (String extension : FXUtils.IMAGE_EXTENSIONS) {
|
||||
image = tryLoadImage(Paths.get("background." + extension));
|
||||
if (image != null)
|
||||
return image;
|
||||
}
|
||||
if (!image.isPresent()) {
|
||||
image = tryLoadImage(Paths.get("background.jpg"));
|
||||
}
|
||||
if (!image.isPresent()) {
|
||||
image = tryLoadImage(Paths.get("background.gif"));
|
||||
}
|
||||
return image.orElseGet(() -> newBuiltinImage("/assets/img/background.jpg"));
|
||||
|
||||
return newBuiltinImage("/assets/img/background.jpg");
|
||||
}
|
||||
|
||||
private Optional<Image> randomImageIn(Path imageDir) {
|
||||
private @Nullable Image randomImageIn(Path imageDir) {
|
||||
if (!Files.isDirectory(imageDir)) {
|
||||
return Optional.empty();
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Path> candidates;
|
||||
try (Stream<Path> stream = Files.list(imageDir)) {
|
||||
candidates = stream
|
||||
.filter(it -> FXUtils.IMAGE_EXTENSIONS.contains(getExtension(it).toLowerCase(Locale.ROOT)))
|
||||
.filter(Files::isReadable)
|
||||
.filter(it -> {
|
||||
String ext = getExtension(it).toLowerCase(Locale.ROOT);
|
||||
return ext.equals("png") || ext.equals("jpg") || ext.equals("gif");
|
||||
})
|
||||
.collect(toList());
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to list files in ./bg", e);
|
||||
return Optional.empty();
|
||||
return null;
|
||||
}
|
||||
|
||||
Random rnd = new Random();
|
||||
while (candidates.size() > 0) {
|
||||
while (!candidates.isEmpty()) {
|
||||
int selected = rnd.nextInt(candidates.size());
|
||||
Optional<Image> loaded = tryLoadImage(candidates.get(selected));
|
||||
if (loaded.isPresent()) {
|
||||
Image loaded = tryLoadImage(candidates.get(selected));
|
||||
if (loaded != null)
|
||||
return loaded;
|
||||
} else {
|
||||
else
|
||||
candidates.remove(selected);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
return null;
|
||||
}
|
||||
|
||||
private Optional<Image> tryLoadImage(Path path) {
|
||||
private @Nullable Image tryLoadImage(Path path) {
|
||||
if (!Files.isReadable(path))
|
||||
return Optional.empty();
|
||||
return null;
|
||||
|
||||
return tryLoadImage(path.toAbsolutePath().toUri().toString());
|
||||
}
|
||||
|
||||
private Optional<Image> tryLoadImage(String url) {
|
||||
Image img;
|
||||
try {
|
||||
img = new Image(url);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return FXUtils.loadImage(path);
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Couldn't load background image", e);
|
||||
return Optional.empty();
|
||||
return null;
|
||||
}
|
||||
|
||||
if (img.getException() != null) {
|
||||
LOG.warning("Couldn't load background image", img.getException());
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of(img);
|
||||
}
|
||||
|
||||
// ==== Navigation ====
|
||||
@@ -383,7 +380,7 @@ public class DecoratorController {
|
||||
return;
|
||||
}
|
||||
dialog = new JFXDialog();
|
||||
dialogPane = new StackContainerPane();
|
||||
dialogPane = new JFXDialogPane();
|
||||
|
||||
dialog.setContent(dialogPane);
|
||||
decorator.capableDraggingWindow(dialog);
|
||||
@@ -426,18 +423,21 @@ public class DecoratorController {
|
||||
.ifPresent(handler -> node.removeEventHandler(DialogCloseEvent.CLOSE, (EventHandler<DialogCloseEvent>) handler));
|
||||
|
||||
if (dialog != null) {
|
||||
dialogPane.pop(node);
|
||||
JFXDialogPane pane = dialogPane;
|
||||
|
||||
if (node instanceof DialogAware) {
|
||||
((DialogAware) node).onDialogClosed();
|
||||
}
|
||||
|
||||
if (dialogPane.getChildren().isEmpty()) {
|
||||
if (pane.size() == 1 && pane.peek().orElse(null) == node) {
|
||||
dialog.setOnDialogClosed(e -> pane.pop(node));
|
||||
dialog.close();
|
||||
dialog = null;
|
||||
dialogPane = null;
|
||||
|
||||
navigator.setDisable(false);
|
||||
} else {
|
||||
pane.pop(node);
|
||||
}
|
||||
|
||||
if (node instanceof DialogAware) {
|
||||
((DialogAware) node).onDialogClosed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,10 +18,8 @@
|
||||
package org.jackhuang.hmcl.ui.decorator;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.svg.SVGGlyph;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Bounds;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
@@ -29,11 +27,10 @@ import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.effect.BlurType;
|
||||
import javafx.scene.effect.DropShadow;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.stage.Stage;
|
||||
@@ -44,20 +41,14 @@ import org.jackhuang.hmcl.ui.animation.AnimationProducer;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.wizard.Navigation;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
private static final PseudoClass TRANSPARENT = PseudoClass.getPseudoClass("transparent");
|
||||
private static final SVGGlyph minus = Lang.apply(new SVGGlyph(0, "MINUS", "M804.571 420.571v109.714q0 22.857-16 38.857t-38.857 16h-694.857q-22.857 0-38.857-16t-16-38.857v-109.714q0-22.857 16-38.857t38.857-16h694.857q22.857 0 38.857 16t16 38.857z", Color.WHITE),
|
||||
glyph -> { glyph.setSize(12, 2); glyph.setTranslateY(4); });
|
||||
|
||||
private final StackPane root, parent;
|
||||
private final StackPane titleContainer;
|
||||
private final Stage primaryStage;
|
||||
private final TransitionPane navBarPane;
|
||||
|
||||
private double xOffset, yOffset, newX, newY, initX, initY;
|
||||
private boolean titleBarTransparent = true;
|
||||
private double mouseInitX, mouseInitY, stageInitX, stageInitY, stageInitWidth, stageInitHeight;
|
||||
|
||||
/**
|
||||
* Constructor for all SkinBase instances.
|
||||
@@ -69,14 +60,13 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
|
||||
primaryStage = control.getPrimaryStage();
|
||||
|
||||
minus.fillProperty().bind(Theme.foregroundFillBinding());
|
||||
|
||||
Decorator skinnable = getSkinnable();
|
||||
root = new StackPane();
|
||||
root.getStyleClass().add("window");
|
||||
|
||||
StackPane shadowContainer = new StackPane();
|
||||
shadowContainer.getStyleClass().add("body");
|
||||
shadowContainer.setEffect(new DropShadow(BlurType.ONE_PASS_BOX, Color.rgb(0, 0, 0, 0.4), 10, 0.3, 0.0, 0.0));
|
||||
|
||||
parent = new StackPane();
|
||||
Rectangle clip = new Rectangle();
|
||||
@@ -197,18 +187,19 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
buttonsContainer.setMaxHeight(40);
|
||||
{
|
||||
JFXButton btnHelp = new JFXButton();
|
||||
btnHelp.setFocusTraversable(false);
|
||||
btnHelp.setGraphic(SVG.HELP_CIRCLE_OUTLINE.createIcon(Theme.foregroundFillBinding(), -1, -1));
|
||||
btnHelp.getStyleClass().add("jfx-decorator-button");
|
||||
btnHelp.setOnAction(e -> FXUtils.openLink("https://docs.hmcl.net/help.html"));
|
||||
|
||||
JFXButton btnMin = new JFXButton();
|
||||
StackPane pane = new StackPane(minus);
|
||||
pane.setAlignment(Pos.CENTER);
|
||||
btnMin.setGraphic(pane);
|
||||
btnMin.setFocusTraversable(false);
|
||||
btnMin.setGraphic(SVG.MINUS.createIcon(Theme.foregroundFillBinding(), -1, -1));
|
||||
btnMin.getStyleClass().add("jfx-decorator-button");
|
||||
btnMin.setOnAction(e -> skinnable.minimize());
|
||||
|
||||
JFXButton btnClose = new JFXButton();
|
||||
btnClose.setFocusTraversable(false);
|
||||
btnClose.setGraphic(SVG.CLOSE.createIcon(Theme.foregroundFillBinding(), -1, -1));
|
||||
btnClose.getStyleClass().add("jfx-decorator-button");
|
||||
btnClose.setOnAction(e -> skinnable.close());
|
||||
@@ -236,6 +227,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
|
||||
if (canBack) {
|
||||
JFXButton backNavButton = new JFXButton();
|
||||
backNavButton.setFocusTraversable(false);
|
||||
backNavButton.setGraphic(SVG.BACK.createIcon(Theme.foregroundFillBinding(), -1, -1));
|
||||
backNavButton.getStyleClass().add("jfx-decorator-button");
|
||||
backNavButton.ripplerFillProperty().set(Theme.whiteFill());
|
||||
@@ -247,6 +239,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
|
||||
if (canClose) {
|
||||
JFXButton closeNavButton = new JFXButton();
|
||||
closeNavButton.setFocusTraversable(false);
|
||||
closeNavButton.setGraphic(SVG.CLOSE.createIcon(Theme.foregroundFillBinding(), -1, -1));
|
||||
closeNavButton.getStyleClass().add("jfx-decorator-button");
|
||||
closeNavButton.ripplerFillProperty().set(Theme.whiteFill());
|
||||
@@ -270,7 +263,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
titleLabel.getStyleClass().add("jfx-decorator-title");
|
||||
if (titleNode == null) {
|
||||
titleLabel.maxWidthProperty().bind(Bindings.createDoubleBinding(
|
||||
() -> skinnable.getWidth() - 100 - navLeft.getWidth(),
|
||||
() -> skinnable.getWidth() - 150 - navLeft.getWidth(),
|
||||
skinnable.widthProperty(), navLeft.widthProperty()));
|
||||
} else {
|
||||
titleLabel.prefWidthProperty().bind(Bindings.createDoubleBinding(() -> {
|
||||
@@ -310,13 +303,6 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
return navBar;
|
||||
}
|
||||
|
||||
private void updateInitMouseValues(MouseEvent mouseEvent) {
|
||||
initX = mouseEvent.getScreenX();
|
||||
initY = mouseEvent.getScreenY();
|
||||
xOffset = mouseEvent.getSceneX();
|
||||
yOffset = mouseEvent.getSceneY();
|
||||
}
|
||||
|
||||
private boolean isRightEdge(double x, double y, Bounds boundsInParent) {
|
||||
return x < root.getWidth() && x >= root.getWidth() - root.snappedLeftInset();
|
||||
}
|
||||
@@ -333,146 +319,139 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
return x >= 0 && x <= root.snappedLeftInset();
|
||||
}
|
||||
|
||||
private boolean setStageWidth(double width) {
|
||||
if (width >= primaryStage.getMinWidth() && width >= titleContainer.getMinWidth()) {
|
||||
primaryStage.setWidth(width);
|
||||
initX = newX;
|
||||
return true;
|
||||
} else {
|
||||
if (width >= primaryStage.getMinWidth() && width <= titleContainer.getMinWidth())
|
||||
primaryStage.setWidth(titleContainer.getMinWidth());
|
||||
private void resizeStage(double newWidth, double newHeight) {
|
||||
if (newWidth < 0)
|
||||
newWidth = primaryStage.getWidth();
|
||||
if (newWidth < primaryStage.getMinWidth())
|
||||
newWidth = primaryStage.getMinWidth();
|
||||
if (newWidth < titleContainer.getMinWidth())
|
||||
newWidth = titleContainer.getMinWidth();
|
||||
|
||||
return false;
|
||||
}
|
||||
if (newHeight < 0)
|
||||
newHeight = primaryStage.getHeight();
|
||||
if (newHeight < primaryStage.getMinHeight())
|
||||
newHeight = primaryStage.getMinHeight();
|
||||
if (newHeight < titleContainer.getMinHeight())
|
||||
newHeight = titleContainer.getMinHeight();
|
||||
|
||||
// Width and height must be set simultaneously to avoid JDK-8344372 (https://github.com/openjdk/jfx/pull/1654)
|
||||
primaryStage.setWidth(newWidth);
|
||||
primaryStage.setHeight(newHeight);
|
||||
}
|
||||
|
||||
private boolean setStageHeight(double height) {
|
||||
if (height >= primaryStage.getMinHeight() && height >= titleContainer.getHeight()) {
|
||||
primaryStage.setHeight(height);
|
||||
initY = newY;
|
||||
return true;
|
||||
} else {
|
||||
if (height >= primaryStage.getMinHeight() && height <= titleContainer.getHeight())
|
||||
primaryStage.setHeight(titleContainer.getHeight());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ====
|
||||
|
||||
protected void onMouseMoved(MouseEvent mouseEvent) {
|
||||
if (!primaryStage.isFullScreen()) {
|
||||
updateInitMouseValues(mouseEvent);
|
||||
if (primaryStage.isResizable()) {
|
||||
double x = mouseEvent.getX(), y = mouseEvent.getY();
|
||||
Bounds boundsInParent = root.getBoundsInParent();
|
||||
double diagonalSize = root.snappedLeftInset() + 10;
|
||||
if (this.isRightEdge(x, y, boundsInParent)) {
|
||||
if (y < diagonalSize) {
|
||||
root.setCursor(Cursor.NE_RESIZE);
|
||||
} else if (y > root.getHeight() - diagonalSize) {
|
||||
root.setCursor(Cursor.SE_RESIZE);
|
||||
} else {
|
||||
root.setCursor(Cursor.E_RESIZE);
|
||||
}
|
||||
} else if (this.isLeftEdge(x, y, boundsInParent)) {
|
||||
if (y < diagonalSize) {
|
||||
root.setCursor(Cursor.NW_RESIZE);
|
||||
} else if (y > root.getHeight() - diagonalSize) {
|
||||
root.setCursor(Cursor.SW_RESIZE);
|
||||
} else {
|
||||
root.setCursor(Cursor.W_RESIZE);
|
||||
}
|
||||
} else if (this.isTopEdge(x, y, boundsInParent)) {
|
||||
if (x < diagonalSize) {
|
||||
root.setCursor(Cursor.NW_RESIZE);
|
||||
} else if (x > root.getWidth() - diagonalSize) {
|
||||
root.setCursor(Cursor.NE_RESIZE);
|
||||
} else {
|
||||
root.setCursor(Cursor.N_RESIZE);
|
||||
}
|
||||
} else if (this.isBottomEdge(x, y, boundsInParent)) {
|
||||
if (x < diagonalSize) {
|
||||
root.setCursor(Cursor.SW_RESIZE);
|
||||
} else if (x > root.getWidth() - diagonalSize) {
|
||||
root.setCursor(Cursor.SE_RESIZE);
|
||||
} else {
|
||||
root.setCursor(Cursor.S_RESIZE);
|
||||
}
|
||||
private void onMouseMoved(MouseEvent mouseEvent) {
|
||||
if (!primaryStage.isFullScreen() && primaryStage.isResizable()) {
|
||||
double x = mouseEvent.getX(), y = mouseEvent.getY();
|
||||
Bounds boundsInParent = root.getBoundsInParent();
|
||||
double diagonalSize = root.snappedLeftInset() + 10;
|
||||
if (this.isRightEdge(x, y, boundsInParent)) {
|
||||
if (y < diagonalSize) {
|
||||
root.setCursor(Cursor.NE_RESIZE);
|
||||
} else if (y > root.getHeight() - diagonalSize) {
|
||||
root.setCursor(Cursor.SE_RESIZE);
|
||||
} else {
|
||||
root.setCursor(Cursor.DEFAULT);
|
||||
root.setCursor(Cursor.E_RESIZE);
|
||||
}
|
||||
} else if (this.isLeftEdge(x, y, boundsInParent)) {
|
||||
if (y < diagonalSize) {
|
||||
root.setCursor(Cursor.NW_RESIZE);
|
||||
} else if (y > root.getHeight() - diagonalSize) {
|
||||
root.setCursor(Cursor.SW_RESIZE);
|
||||
} else {
|
||||
root.setCursor(Cursor.W_RESIZE);
|
||||
}
|
||||
} else if (this.isTopEdge(x, y, boundsInParent)) {
|
||||
if (x < diagonalSize) {
|
||||
root.setCursor(Cursor.NW_RESIZE);
|
||||
} else if (x > root.getWidth() - diagonalSize) {
|
||||
root.setCursor(Cursor.NE_RESIZE);
|
||||
} else {
|
||||
root.setCursor(Cursor.N_RESIZE);
|
||||
}
|
||||
} else if (this.isBottomEdge(x, y, boundsInParent)) {
|
||||
if (x < diagonalSize) {
|
||||
root.setCursor(Cursor.SW_RESIZE);
|
||||
} else if (x > root.getWidth() - diagonalSize) {
|
||||
root.setCursor(Cursor.SE_RESIZE);
|
||||
} else {
|
||||
root.setCursor(Cursor.S_RESIZE);
|
||||
}
|
||||
} else {
|
||||
root.setCursor(Cursor.DEFAULT);
|
||||
}
|
||||
} else {
|
||||
root.setCursor(Cursor.DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onMouseReleased(MouseEvent mouseEvent) {
|
||||
private void onMouseReleased(MouseEvent mouseEvent) {
|
||||
getSkinnable().setDragging(false);
|
||||
}
|
||||
|
||||
protected void onMouseDragged(MouseEvent mouseEvent) {
|
||||
getSkinnable().setDragging(true);
|
||||
if (mouseEvent.isPrimaryButtonDown() && (this.xOffset != -1.0 || this.yOffset != -1.0)) {
|
||||
if (!this.primaryStage.isFullScreen() && !mouseEvent.isStillSincePress()) {
|
||||
this.newX = mouseEvent.getScreenX();
|
||||
this.newY = mouseEvent.getScreenY();
|
||||
double deltaX = this.newX - this.initX;
|
||||
double deltaY = this.newY - this.initY;
|
||||
Cursor cursor = root.getCursor();
|
||||
if (Cursor.E_RESIZE == cursor) {
|
||||
this.setStageWidth(this.primaryStage.getWidth() + deltaX);
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.NE_RESIZE == cursor) {
|
||||
if (this.setStageHeight(this.primaryStage.getHeight() - deltaY)) {
|
||||
this.primaryStage.setY(this.primaryStage.getY() + deltaY);
|
||||
}
|
||||
private void onMouseDragged(MouseEvent mouseEvent) {
|
||||
if (!getSkinnable().isDragging()) {
|
||||
getSkinnable().setDragging(true);
|
||||
mouseInitX = mouseEvent.getScreenX();
|
||||
mouseInitY = mouseEvent.getScreenY();
|
||||
stageInitX = primaryStage.getX();
|
||||
stageInitY = primaryStage.getY();
|
||||
stageInitWidth = primaryStage.getWidth();
|
||||
stageInitHeight = primaryStage.getHeight();
|
||||
}
|
||||
|
||||
this.setStageWidth(this.primaryStage.getWidth() + deltaX);
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.SE_RESIZE == cursor) {
|
||||
this.setStageWidth(this.primaryStage.getWidth() + deltaX);
|
||||
this.setStageHeight(this.primaryStage.getHeight() + deltaY);
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.S_RESIZE == cursor) {
|
||||
this.setStageHeight(this.primaryStage.getHeight() + deltaY);
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.W_RESIZE == cursor) {
|
||||
if (this.setStageWidth(this.primaryStage.getWidth() - deltaX)) {
|
||||
this.primaryStage.setX(this.primaryStage.getX() + deltaX);
|
||||
}
|
||||
if (primaryStage.isFullScreen() || !mouseEvent.isPrimaryButtonDown() || mouseEvent.isStillSincePress())
|
||||
return;
|
||||
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.SW_RESIZE == cursor) {
|
||||
if (this.setStageWidth(this.primaryStage.getWidth() - deltaX)) {
|
||||
this.primaryStage.setX(this.primaryStage.getX() + deltaX);
|
||||
}
|
||||
double dx = mouseEvent.getScreenX() - mouseInitX;
|
||||
double dy = mouseEvent.getScreenY() - mouseInitY;
|
||||
|
||||
this.setStageHeight(this.primaryStage.getHeight() + deltaY);
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.NW_RESIZE == cursor) {
|
||||
if (this.setStageWidth(this.primaryStage.getWidth() - deltaX)) {
|
||||
this.primaryStage.setX(this.primaryStage.getX() + deltaX);
|
||||
}
|
||||
Cursor cursor = root.getCursor();
|
||||
if (getSkinnable().isAllowMove()) {
|
||||
if (cursor == Cursor.DEFAULT) {
|
||||
primaryStage.setX(stageInitX + dx);
|
||||
primaryStage.setY(stageInitY + dy);
|
||||
mouseEvent.consume();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.setStageHeight(this.primaryStage.getHeight() - deltaY)) {
|
||||
this.primaryStage.setY(this.primaryStage.getY() + deltaY);
|
||||
}
|
||||
if (getSkinnable().isResizable()) {
|
||||
if (cursor == Cursor.E_RESIZE) {
|
||||
resizeStage(stageInitWidth + dx, -1);
|
||||
mouseEvent.consume();
|
||||
|
||||
mouseEvent.consume();
|
||||
} else if (Cursor.N_RESIZE == cursor) {
|
||||
if (this.setStageHeight(this.primaryStage.getHeight() - deltaY)) {
|
||||
this.primaryStage.setY(this.primaryStage.getY() + deltaY);
|
||||
}
|
||||
} else if (cursor == Cursor.S_RESIZE) {
|
||||
resizeStage(-1, stageInitHeight + dy);
|
||||
mouseEvent.consume();
|
||||
|
||||
mouseEvent.consume();
|
||||
} else if (getSkinnable().isAllowMove()) {
|
||||
this.primaryStage.setX(mouseEvent.getScreenX() - this.xOffset);
|
||||
this.primaryStage.setY(mouseEvent.getScreenY() - this.yOffset);
|
||||
mouseEvent.consume();
|
||||
}
|
||||
} else if (cursor == Cursor.W_RESIZE) {
|
||||
resizeStage(stageInitWidth - dx, -1);
|
||||
primaryStage.setX(stageInitX + stageInitWidth - primaryStage.getWidth());
|
||||
mouseEvent.consume();
|
||||
|
||||
} else if (cursor == Cursor.N_RESIZE) {
|
||||
resizeStage(-1, stageInitHeight - dy);
|
||||
primaryStage.setY(stageInitY + stageInitHeight - primaryStage.getHeight());
|
||||
mouseEvent.consume();
|
||||
|
||||
} else if (cursor == Cursor.SE_RESIZE) {
|
||||
resizeStage(stageInitWidth + dx, stageInitHeight + dy);
|
||||
mouseEvent.consume();
|
||||
|
||||
} else if (cursor == Cursor.SW_RESIZE) {
|
||||
resizeStage(stageInitWidth - dx, stageInitHeight + dy);
|
||||
primaryStage.setX(stageInitX + stageInitWidth - primaryStage.getWidth());
|
||||
mouseEvent.consume();
|
||||
|
||||
} else if (cursor == Cursor.NW_RESIZE) {
|
||||
resizeStage(stageInitWidth - dx, stageInitHeight - dy);
|
||||
primaryStage.setX(stageInitX + stageInitWidth - primaryStage.getWidth());
|
||||
primaryStage.setY(stageInitY + stageInitHeight - primaryStage.getHeight());
|
||||
mouseEvent.consume();
|
||||
|
||||
} else if (cursor == Cursor.NE_RESIZE) {
|
||||
resizeStage(stageInitWidth + dx, stageInitHeight - dy);
|
||||
primaryStage.setY(stageInitY + stageInitHeight - primaryStage.getHeight());
|
||||
mouseEvent.consume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,6 @@ import java.util.concurrent.CancellationException;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
@@ -100,43 +99,16 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE);
|
||||
});
|
||||
|
||||
{
|
||||
AdvancedListBox sideBar = new AdvancedListBox()
|
||||
.startCategory(i18n("download.game").toUpperCase(Locale.ROOT))
|
||||
.addNavigationDrawerItem(item -> {
|
||||
item.setTitle(i18n("game"));
|
||||
item.setLeftGraphic(wrap(SVG.GAMEPAD));
|
||||
item.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(newGameTab));
|
||||
item.setOnAction(e -> tab.select(newGameTab));
|
||||
})
|
||||
.addNavigationDrawerItem(settingsItem -> {
|
||||
settingsItem.setTitle(i18n("modpack"));
|
||||
settingsItem.setLeftGraphic(wrap(SVG.PACK));
|
||||
settingsItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(modpackTab));
|
||||
settingsItem.setOnAction(e -> tab.select(modpackTab));
|
||||
})
|
||||
.startCategory(i18n("download.content").toUpperCase(Locale.ROOT))
|
||||
.addNavigationDrawerItem(item -> {
|
||||
item.setTitle(i18n("mods"));
|
||||
item.setLeftGraphic(wrap(SVG.PUZZLE));
|
||||
item.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(modTab));
|
||||
item.setOnAction(e -> tab.select(modTab));
|
||||
})
|
||||
.addNavigationDrawerItem(item -> {
|
||||
item.setTitle(i18n("resourcepack"));
|
||||
item.setLeftGraphic(wrap(SVG.TEXTURE_BOX));
|
||||
item.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(resourcePackTab));
|
||||
item.setOnAction(e -> tab.select(resourcePackTab));
|
||||
})
|
||||
.addNavigationDrawerItem(item -> {
|
||||
item.setTitle(i18n("world"));
|
||||
item.setLeftGraphic(wrap(SVG.EARTH));
|
||||
item.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(worldTab));
|
||||
item.setOnAction(e -> selectTabIfCurseForgeAvailable(worldTab));
|
||||
});
|
||||
FXUtils.setLimitWidth(sideBar, 200);
|
||||
setLeft(sideBar);
|
||||
}
|
||||
AdvancedListBox sideBar = new AdvancedListBox()
|
||||
.startCategory(i18n("download.game").toUpperCase(Locale.ROOT))
|
||||
.addNavigationDrawerTab(tab, newGameTab, i18n("game"), SVG.GAMEPAD)
|
||||
.addNavigationDrawerTab(tab, modpackTab, i18n("modpack"), SVG.PACK)
|
||||
.startCategory(i18n("download.content").toUpperCase(Locale.ROOT))
|
||||
.addNavigationDrawerTab(tab, modTab, i18n("mods"), SVG.PUZZLE)
|
||||
.addNavigationDrawerTab(tab, resourcePackTab, i18n("resourcepack"), SVG.TEXTURE_BOX)
|
||||
.addNavigationDrawerTab(tab, worldTab, i18n("world"), SVG.EARTH);
|
||||
FXUtils.setLimitWidth(sideBar, 200);
|
||||
setLeft(sideBar);
|
||||
|
||||
setCenter(transitionPane);
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
|
||||
import static javafx.beans.binding.Bindings.createBooleanBinding;
|
||||
@@ -121,8 +122,25 @@ public class InstallersPage extends Control implements WizardPage {
|
||||
}
|
||||
|
||||
protected void onInstall() {
|
||||
controller.getSettings().put("name", txtName.getText());
|
||||
controller.onFinish();
|
||||
String name = txtName.getText();
|
||||
|
||||
// Check for non-ASCII characters.
|
||||
if (!StandardCharsets.US_ASCII.newEncoder().canEncode(name)) {
|
||||
Controllers.dialog(new MessageDialogPane.Builder(
|
||||
i18n("install.name.invalid"),
|
||||
i18n("message.warning"),
|
||||
MessageDialogPane.MessageType.QUESTION)
|
||||
.yesOrNo(() -> {
|
||||
controller.getSettings().put("name", name);
|
||||
controller.onFinish();
|
||||
}, () -> {
|
||||
// The user selects Cancel and does nothing.
|
||||
})
|
||||
.build());
|
||||
} else {
|
||||
controller.getSettings().put("name", name);
|
||||
controller.onFinish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -42,6 +42,7 @@ import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -149,10 +150,27 @@ public final class LocalModpackPage extends ModpackPage {
|
||||
}
|
||||
|
||||
protected void onInstall() {
|
||||
if (!txtModpackName.validate()) return;
|
||||
controller.getSettings().put(MODPACK_NAME, txtModpackName.getText());
|
||||
controller.getSettings().put(MODPACK_CHARSET, charset);
|
||||
controller.onFinish();
|
||||
String name = txtModpackName.getText();
|
||||
|
||||
// Check for non-ASCII characters.
|
||||
if (!StandardCharsets.US_ASCII.newEncoder().canEncode(name)) {
|
||||
Controllers.dialog(new MessageDialogPane.Builder(
|
||||
i18n("install.name.invalid"),
|
||||
i18n("message.warning"),
|
||||
MessageDialogPane.MessageType.QUESTION)
|
||||
.yesOrNo(() -> {
|
||||
controller.getSettings().put(MODPACK_NAME, name);
|
||||
controller.getSettings().put(MODPACK_CHARSET, charset);
|
||||
controller.onFinish();
|
||||
}, () -> {
|
||||
// The user selects Cancel and does nothing.
|
||||
})
|
||||
.build());
|
||||
} else {
|
||||
controller.getSettings().put(MODPACK_NAME, name);
|
||||
controller.getSettings().put(MODPACK_CHARSET, charset);
|
||||
controller.onFinish();
|
||||
}
|
||||
}
|
||||
|
||||
protected void onDescribe() {
|
||||
|
||||
@@ -21,12 +21,20 @@ import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXCheckBox;
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import com.jfoenix.controls.JFXSpinner;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import javafx.animation.PauseTransition;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.download.VersionList;
|
||||
@@ -53,15 +61,21 @@ import org.jackhuang.hmcl.ui.wizard.Navigation;
|
||||
import org.jackhuang.hmcl.ui.wizard.Refreshable;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.Holder;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.ignoreEvent;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
|
||||
import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.wrap;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.formatDateTime;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class VersionsPage extends BorderPane implements WizardPage, Refreshable {
|
||||
@@ -78,14 +92,15 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
private final JFXCheckBox chkRelease;
|
||||
private final JFXCheckBox chkSnapshot;
|
||||
private final JFXCheckBox chkOld;
|
||||
private final JFXButton btnRefresh;
|
||||
private final HBox checkPane;
|
||||
private final ComponentList centrePane;
|
||||
private final StackPane center;
|
||||
|
||||
private final VersionList<?> versionList;
|
||||
private CompletableFuture<?> executor;
|
||||
|
||||
private final HBox searchBar;
|
||||
private final StringProperty queryString = new SimpleStringProperty();
|
||||
|
||||
public VersionsPage(Navigation navigation, String title, String gameVersion, DownloadProvider downloadProvider, String libraryId, Runnable callback) {
|
||||
this.title = title;
|
||||
this.gameVersion = gameVersion;
|
||||
@@ -100,6 +115,8 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
this.setTop(hintPane);
|
||||
|
||||
root = new TransitionPane();
|
||||
BorderPane toolbarPane = new BorderPane();
|
||||
JFXButton btnRefresh;
|
||||
{
|
||||
spinner = new JFXSpinner();
|
||||
|
||||
@@ -109,7 +126,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
centrePane = new ComponentList();
|
||||
centrePane.getStyleClass().add("no-padding");
|
||||
{
|
||||
checkPane = new HBox();
|
||||
HBox checkPane = new HBox();
|
||||
checkPane.setSpacing(10);
|
||||
{
|
||||
chkRelease = new JFXCheckBox(i18n("version.game.releases"));
|
||||
@@ -122,21 +139,69 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
chkOld = new JFXCheckBox(i18n("version.game.old"));
|
||||
HBox.setMargin(chkOld, new Insets(10, 0, 10, 0));
|
||||
|
||||
HBox pane = new HBox();
|
||||
HBox.setHgrow(pane, Priority.ALWAYS);
|
||||
|
||||
btnRefresh = new JFXButton(i18n("button.refresh"));
|
||||
btnRefresh.getStyleClass().add("jfx-tool-bar-button");
|
||||
btnRefresh.setOnAction(e -> onRefresh());
|
||||
|
||||
checkPane.getChildren().setAll(chkRelease, chkSnapshot, chkOld, pane, btnRefresh);
|
||||
checkPane.getChildren().setAll(chkRelease, chkSnapshot, chkOld);
|
||||
}
|
||||
|
||||
list = new JFXListView<>();
|
||||
list.getStyleClass().add("jfx-list-view-float");
|
||||
VBox.setVgrow(list, Priority.ALWAYS);
|
||||
|
||||
centrePane.getContent().setAll(checkPane, list);
|
||||
TransitionPane rightToolbarPane = new TransitionPane();
|
||||
{
|
||||
HBox refreshPane = new HBox();
|
||||
refreshPane.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
btnRefresh = new JFXButton(i18n("button.refresh"));
|
||||
btnRefresh.getStyleClass().add("jfx-tool-bar-button");
|
||||
btnRefresh.setOnAction(e -> onRefresh());
|
||||
|
||||
JFXButton btnSearch = new JFXButton(i18n("search"));
|
||||
btnSearch.getStyleClass().add("jfx-tool-bar-button");
|
||||
btnSearch.setGraphic(wrap(SVG.MAGNIFY.createIcon(Theme.blackFill(), -1, -1)));
|
||||
|
||||
searchBar = new HBox();
|
||||
{
|
||||
searchBar.setAlignment(Pos.CENTER);
|
||||
searchBar.setPadding(new Insets(0, 5, 0, 0));
|
||||
|
||||
JFXTextField searchField = new JFXTextField();
|
||||
searchField.setPromptText(i18n("search"));
|
||||
HBox.setHgrow(searchField, Priority.ALWAYS);
|
||||
|
||||
JFXButton closeSearchBar = new JFXButton();
|
||||
closeSearchBar.getStyleClass().add("jfx-tool-bar-button");
|
||||
closeSearchBar.setGraphic(wrap(SVG.CLOSE.createIcon(Theme.blackFill(), -1, -1)));
|
||||
closeSearchBar.setOnAction(e -> {
|
||||
searchField.clear();
|
||||
rightToolbarPane.setContent(refreshPane, ContainerAnimations.FADE);
|
||||
});
|
||||
onEscPressed(searchField, closeSearchBar::fire);
|
||||
PauseTransition pause = new PauseTransition(Duration.millis(100));
|
||||
pause.setOnFinished(e -> queryString.set(searchField.getText()));
|
||||
searchField.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
pause.setRate(1);
|
||||
pause.playFromStart();
|
||||
});
|
||||
|
||||
searchBar.getChildren().setAll(searchField, closeSearchBar);
|
||||
|
||||
btnSearch.setOnAction(e -> {
|
||||
rightToolbarPane.setContent(searchBar, ContainerAnimations.FADE);
|
||||
searchField.requestFocus();
|
||||
});
|
||||
}
|
||||
|
||||
refreshPane.getChildren().setAll(new HBox(btnSearch, btnRefresh));
|
||||
rightToolbarPane.setContent(refreshPane, ContainerAnimations.NONE);
|
||||
}
|
||||
|
||||
// ListViewBehavior would consume ESC pressed event, preventing us from handling it, so we ignore it here
|
||||
ignoreEvent(list, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE);
|
||||
|
||||
toolbarPane.setLeft(checkPane);
|
||||
toolbarPane.setRight(rightToolbarPane);
|
||||
|
||||
centrePane.getContent().setAll(toolbarPane, list);
|
||||
}
|
||||
|
||||
center.getChildren().setAll(centrePane);
|
||||
@@ -163,22 +228,53 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
this.setCenter(root);
|
||||
|
||||
versionList = downloadProvider.getVersionListById(libraryId);
|
||||
if (versionList.hasType()) {
|
||||
centrePane.getContent().setAll(checkPane, list);
|
||||
boolean hasType = versionList.hasType();
|
||||
chkRelease.setManaged(hasType);
|
||||
chkRelease.setVisible(hasType);
|
||||
chkSnapshot.setManaged(hasType);
|
||||
chkSnapshot.setVisible(hasType);
|
||||
chkOld.setManaged(hasType);
|
||||
chkOld.setVisible(hasType);
|
||||
|
||||
if (hasType) {
|
||||
centrePane.getContent().setAll(toolbarPane, list);
|
||||
} else {
|
||||
centrePane.getContent().setAll(list);
|
||||
}
|
||||
ComponentList.setVgrow(list, Priority.ALWAYS);
|
||||
|
||||
InvalidationListener listener = o -> list.getItems().setAll(loadVersions());
|
||||
InvalidationListener listener = o -> {
|
||||
List<RemoteVersion> versions = loadVersions();
|
||||
String query = queryString.get();
|
||||
if (!StringUtils.isBlank(query)) {
|
||||
Predicate<RemoteVersion> predicate;
|
||||
if (query.startsWith("regex:")) {
|
||||
try {
|
||||
Pattern pattern = Pattern.compile(query.substring("regex:".length()));
|
||||
predicate = it -> pattern.matcher(it.getSelfVersion()).find();
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Illegal regular expression", e);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
String lowerQueryString = query.toLowerCase(Locale.ROOT);
|
||||
predicate = it -> it.getSelfVersion().toLowerCase(Locale.ROOT).contains(lowerQueryString);
|
||||
}
|
||||
|
||||
versions = versions.stream().filter(predicate).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
list.getItems().setAll(versions);
|
||||
};
|
||||
chkRelease.selectedProperty().addListener(listener);
|
||||
chkSnapshot.selectedProperty().addListener(listener);
|
||||
chkOld.selectedProperty().addListener(listener);
|
||||
queryString.addListener(listener);
|
||||
|
||||
btnRefresh.setGraphic(wrap(SVG.REFRESH.createIcon(Theme.blackFill(), -1, -1)));
|
||||
|
||||
Holder<RemoteVersionListCell> lastCell = new Holder<>();
|
||||
list.setCellFactory(listView -> new RemoteVersionListCell(lastCell));
|
||||
list.setCellFactory(listView -> new RemoteVersionListCell(lastCell, libraryId));
|
||||
|
||||
FXUtils.onClicked(list, () -> {
|
||||
if (list.getSelectionModel().getSelectedIndex() < 0)
|
||||
@@ -259,7 +355,9 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
refresh();
|
||||
}
|
||||
|
||||
private void onBack() { navigation.onPrev(true); }
|
||||
private void onBack() {
|
||||
navigation.onPrev(true);
|
||||
}
|
||||
|
||||
private void onSponsor() {
|
||||
FXUtils.openLink("https://bmclapidoc.bangbang93.com");
|
||||
@@ -272,8 +370,12 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
|
||||
private final Holder<RemoteVersionListCell> lastCell;
|
||||
|
||||
RemoteVersionListCell(Holder<RemoteVersionListCell> lastCell) {
|
||||
RemoteVersionListCell(Holder<RemoteVersionListCell> lastCell, String libraryId) {
|
||||
this.lastCell = lastCell;
|
||||
if ("game".equals(libraryId)) {
|
||||
content.getExternalLinkButton().setGraphic(SVG.EARTH.createIcon(Theme.blackFill(), -1, -1));
|
||||
FXUtils.installFastTooltip(content.getExternalLinkButton(), i18n("wiki.tooltip"));
|
||||
}
|
||||
|
||||
pane.getStyleClass().add("md-list-cell");
|
||||
StackPane.setMargin(content, new Insets(10, 16, 10, 16));
|
||||
@@ -297,7 +399,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
|
||||
content.setTitle(remoteVersion.getSelfVersion());
|
||||
if (remoteVersion.getReleaseDate() != null) {
|
||||
content.setSubtitle(formatDateTime(remoteVersion.getReleaseDate()));
|
||||
content.setSubtitle(I18n.formatDateTime(remoteVersion.getReleaseDate()));
|
||||
} else {
|
||||
content.setSubtitle(null);
|
||||
}
|
||||
@@ -307,14 +409,19 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
case RELEASE:
|
||||
content.getTags().setAll(i18n("version.game.release"));
|
||||
content.setImage(VersionIconType.GRASS.getIcon());
|
||||
content.setExternalLink(i18n("wiki.version.game.release", remoteVersion.getGameVersion()));
|
||||
break;
|
||||
case SNAPSHOT:
|
||||
content.getTags().setAll(i18n("version.game.snapshot"));
|
||||
content.setImage(VersionIconType.COMMAND.getIcon());
|
||||
|
||||
|
||||
content.setExternalLink(i18n("wiki.version.game.snapshot", remoteVersion.getGameVersion()));
|
||||
break;
|
||||
default:
|
||||
content.getTags().setAll(i18n("version.game.old"));
|
||||
content.setImage(VersionIconType.CRAFT_TABLE.getIcon());
|
||||
content.setExternalLink(null);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
@@ -339,6 +446,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
content.setSubtitle(remoteVersion.getGameVersion());
|
||||
else
|
||||
content.getTags().setAll(remoteVersion.getGameVersion());
|
||||
content.setExternalLink(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
@@ -129,17 +130,13 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
if (bg.isDirectory())
|
||||
zip.putDirectory(bg.toPath(), "bg");
|
||||
|
||||
File background_png = new File("background.png").getAbsoluteFile();
|
||||
if (background_png.isFile())
|
||||
zip.putFile(background_png, "background.png");
|
||||
for (String extension : FXUtils.IMAGE_EXTENSIONS) {
|
||||
String fileName = "background." + extension;
|
||||
|
||||
File background_jpg = new File("background.jpg").getAbsoluteFile();
|
||||
if (background_jpg.isFile())
|
||||
zip.putFile(background_jpg, "background.jpg");
|
||||
|
||||
File background_gif = new File("background.gif").getAbsoluteFile();
|
||||
if (background_gif.isFile())
|
||||
zip.putFile(background_gif, "background.gif");
|
||||
File background = new File(fileName).getAbsoluteFile();
|
||||
if (background.isFile())
|
||||
zip.putFile(background, "background.png");
|
||||
}
|
||||
|
||||
zip.putFile(launcherJar, launcherJar.getFileName().toString());
|
||||
}
|
||||
@@ -195,7 +192,8 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
/* overrideJavaArgs */ true,
|
||||
/* overrideConsole */ true,
|
||||
/* overrideCommands */ true,
|
||||
/* overrideWindow */ true
|
||||
/* overrideWindow */ true,
|
||||
/* iconKey */ null // TODO
|
||||
), modpackFile);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
@@ -116,7 +117,7 @@ public final class AboutPage extends StackPane {
|
||||
return componentList;
|
||||
}
|
||||
|
||||
try (Reader reader = new InputStreamReader(input)) {
|
||||
try (Reader reader = new InputStreamReader(input, StandardCharsets.UTF_8)) {
|
||||
JsonArray array = JsonUtils.GSON.fromJson(reader, JsonArray.class);
|
||||
|
||||
for (JsonElement element : array) {
|
||||
|
||||
@@ -48,6 +48,8 @@ import org.jackhuang.hmcl.ui.wizard.SinglePageWizardProvider;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.Platform;
|
||||
|
||||
import java.io.File;
|
||||
@@ -214,6 +216,7 @@ public final class JavaDownloadDialog extends StackPane {
|
||||
distributionBox.setItems(FXCollections.observableList(new ArrayList<>(distributions)));
|
||||
|
||||
FXUtils.onChange(packageTypeBox.getSelectionModel().selectedItemProperty(), packageType -> {
|
||||
|
||||
ObservableList<DiscoJavaRemoteVersion> versions;
|
||||
if (packageType == null
|
||||
|| currentJavaVersionList.get() == null
|
||||
@@ -222,8 +225,26 @@ public final class JavaDownloadDialog extends StackPane {
|
||||
return;
|
||||
}
|
||||
|
||||
DiscoJavaRemoteVersion oldVersion = remoteVersionBox.getSelectionModel().getSelectedItem();
|
||||
remoteVersionBox.setItems(versions);
|
||||
|
||||
if (oldVersion != null) {
|
||||
for (int i = 0; i < versions.size(); i++) {
|
||||
DiscoJavaRemoteVersion version = versions.get(i);
|
||||
if (Objects.equals(version.getDistributionVersion(), oldVersion.getDistributionVersion())) {
|
||||
remoteVersionBox.getSelectionModel().select(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < versions.size(); i++) {
|
||||
DiscoJavaRemoteVersion version = versions.get(i);
|
||||
if (version.getJdkVersion() == oldVersion.getJdkVersion()) {
|
||||
remoteVersionBox.getSelectionModel().select(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < versions.size(); i++) {
|
||||
DiscoJavaRemoteVersion version = versions.get(i);
|
||||
if (version.getJdkVersion() == GameJavaVersion.LATEST.getMajorVersion()) {
|
||||
@@ -289,6 +310,11 @@ public final class JavaDownloadDialog extends StackPane {
|
||||
setHeading(new Label(i18n("java.download")));
|
||||
setBody(body);
|
||||
setActions(warningLabel, downloadButtonPane, cancelButton);
|
||||
if (platform.getOperatingSystem() == OperatingSystem.LINUX && platform.getArchitecture() == Architecture.RISCV64) {
|
||||
JFXHyperlink hyperlink = new JFXHyperlink(i18n("java.download.banshanjdk-8"));
|
||||
hyperlink.setExternalLink("https://www.zthread.cn/#product");
|
||||
getActions().add(0, hyperlink);
|
||||
}
|
||||
}
|
||||
|
||||
private void onDownload() {
|
||||
|
||||
@@ -181,6 +181,7 @@ public final class JavaInstallPage extends WizardSinglePage {
|
||||
pane.setLeft(new Label(name));
|
||||
|
||||
Label valueLabel = FXUtils.truncatedLabel(value, 60);
|
||||
FXUtils.copyOnDoubleClick(valueLabel);
|
||||
BorderPane.setAlignment(valueLabel, Pos.CENTER_RIGHT);
|
||||
pane.setCenter(valueLabel);
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ import org.jackhuang.hmcl.ui.versions.VersionSettingsPage;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class LauncherSettingsPage extends DecoratorAnimatedPage implements DecoratorPage, PageAware {
|
||||
@@ -63,67 +62,25 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
|
||||
tab = new TabHeader(gameTab, javaManagementTab, settingsTab, personalizationTab, downloadTab, helpTab, feedbackTab, aboutTab);
|
||||
|
||||
tab.select(gameTab);
|
||||
gameTab.initializeIfNeeded();
|
||||
gameTab.getNode().loadVersion(Profiles.getSelectedProfile(), null);
|
||||
FXUtils.onChangeAndOperate(tab.getSelectionModel().selectedItemProperty(), newValue -> {
|
||||
transitionPane.setContent(gameTab.getNode(), ContainerAnimations.NONE);
|
||||
FXUtils.onChange(tab.getSelectionModel().selectedItemProperty(), newValue -> {
|
||||
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE);
|
||||
});
|
||||
|
||||
{
|
||||
AdvancedListBox sideBar = new AdvancedListBox()
|
||||
.addNavigationDrawerItem(settingsItem -> {
|
||||
settingsItem.setTitle(i18n("settings.type.global.manage"));
|
||||
settingsItem.setLeftGraphic(wrap(SVG.GAMEPAD));
|
||||
settingsItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(gameTab));
|
||||
settingsItem.setOnAction(e -> tab.select(gameTab));
|
||||
})
|
||||
.addNavigationDrawerItem(javaItem -> {
|
||||
javaItem.setTitle(i18n("java.management"));
|
||||
javaItem.setLeftGraphic(wrap(SVG.WRENCH_OUTLINE));
|
||||
javaItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(javaManagementTab));
|
||||
javaItem.setOnAction(e -> tab.select(javaManagementTab));
|
||||
})
|
||||
.startCategory(i18n("launcher").toUpperCase(Locale.ROOT))
|
||||
.addNavigationDrawerItem(settingsItem -> {
|
||||
settingsItem.setTitle(i18n("settings.launcher.general"));
|
||||
settingsItem.setLeftGraphic(wrap(SVG.APPLICATION_OUTLINE));
|
||||
settingsItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(settingsTab));
|
||||
settingsItem.setOnAction(e -> tab.select(settingsTab));
|
||||
})
|
||||
.addNavigationDrawerItem(personalizationItem -> {
|
||||
personalizationItem.setTitle(i18n("settings.launcher.appearance"));
|
||||
personalizationItem.setLeftGraphic(wrap(SVG.STYLE_OUTLINE));
|
||||
personalizationItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(personalizationTab));
|
||||
personalizationItem.setOnAction(e -> tab.select(personalizationTab));
|
||||
})
|
||||
.addNavigationDrawerItem(downloadItem -> {
|
||||
downloadItem.setTitle(i18n("download"));
|
||||
downloadItem.setLeftGraphic(wrap(SVG.DOWNLOAD_OUTLINE));
|
||||
downloadItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(downloadTab));
|
||||
downloadItem.setOnAction(e -> tab.select(downloadTab));
|
||||
})
|
||||
.startCategory(i18n("help").toUpperCase(Locale.ROOT))
|
||||
.addNavigationDrawerItem(helpItem -> {
|
||||
helpItem.setTitle(i18n("help"));
|
||||
helpItem.setLeftGraphic(wrap(SVG.HELP_CIRCLE_OUTLINE));
|
||||
helpItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(helpTab));
|
||||
helpItem.setOnAction(e -> tab.select(helpTab));
|
||||
})
|
||||
.addNavigationDrawerItem(feedbackItem -> {
|
||||
feedbackItem.setTitle(i18n("feedback"));
|
||||
feedbackItem.setLeftGraphic(wrap(SVG.MESSAGE_ALERT_OUTLINE));
|
||||
feedbackItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(feedbackTab));
|
||||
feedbackItem.setOnAction(e -> tab.select(feedbackTab));
|
||||
})
|
||||
.addNavigationDrawerItem(aboutItem -> {
|
||||
aboutItem.setTitle(i18n("about"));
|
||||
aboutItem.setLeftGraphic(wrap(SVG.INFORMATION_OUTLINE));
|
||||
aboutItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(aboutTab));
|
||||
aboutItem.setOnAction(e -> tab.select(aboutTab));
|
||||
});
|
||||
FXUtils.setLimitWidth(sideBar, 200);
|
||||
setLeft(sideBar);
|
||||
}
|
||||
AdvancedListBox sideBar = new AdvancedListBox()
|
||||
.addNavigationDrawerTab(tab, gameTab, i18n("settings.type.global.manage"), SVG.GAMEPAD)
|
||||
.addNavigationDrawerTab(tab, javaManagementTab, i18n("java.management"), SVG.WRENCH_OUTLINE)
|
||||
.startCategory(i18n("launcher").toUpperCase(Locale.ROOT))
|
||||
.addNavigationDrawerTab(tab, settingsTab, i18n("settings.launcher.general"), SVG.APPLICATION_OUTLINE)
|
||||
.addNavigationDrawerTab(tab, personalizationTab, i18n("settings.launcher.appearance"), SVG.STYLE_OUTLINE)
|
||||
.addNavigationDrawerTab(tab, downloadTab, i18n("download"), SVG.DOWNLOAD_OUTLINE)
|
||||
.startCategory(i18n("help").toUpperCase(Locale.ROOT))
|
||||
.addNavigationDrawerTab(tab, helpTab, i18n("help"), SVG.HELP_CIRCLE_OUTLINE)
|
||||
.addNavigationDrawerTab(tab, feedbackTab, i18n("feedback"), SVG.MESSAGE_ALERT_OUTLINE)
|
||||
.addNavigationDrawerTab(tab, aboutTab, i18n("about"), SVG.INFORMATION_OUTLINE);
|
||||
FXUtils.setLimitWidth(sideBar, 200);
|
||||
setLeft(sideBar);
|
||||
|
||||
setCenter(transitionPane);
|
||||
}
|
||||
|
||||
@@ -28,13 +28,16 @@ import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
@@ -47,7 +50,6 @@ import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.construct.AnnouncementCard;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.PopupMenu;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
@@ -102,23 +104,49 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
setPadding(new Insets(20));
|
||||
|
||||
if (Metadata.isNightly() || (Metadata.isDev() && !Objects.equals(Metadata.VERSION, config().getShownTips().get(ANNOUNCEMENT)))) {
|
||||
AnnouncementCard announcementCard = null;
|
||||
|
||||
String title;
|
||||
String content;
|
||||
if (Metadata.isNightly()) {
|
||||
announcementCard = new AnnouncementCard(i18n("update.channel.nightly.title"), i18n("update.channel.nightly.hint"), null);
|
||||
} else if (Metadata.isDev()) {
|
||||
announcementCard = new AnnouncementCard(i18n("update.channel.dev.title"), i18n("update.channel.dev.hint"), this::hideAnnouncementPane);
|
||||
title = i18n("update.channel.nightly.title");
|
||||
content = i18n("update.channel.nightly.hint");
|
||||
} else {
|
||||
title = i18n("update.channel.dev.title");
|
||||
content = i18n("update.channel.dev.hint");
|
||||
}
|
||||
|
||||
if (announcementCard != null) {
|
||||
VBox announcementBox = new VBox(16);
|
||||
announcementBox.getChildren().add(announcementCard);
|
||||
VBox announcementCard = new VBox();
|
||||
|
||||
announcementPane = new TransitionPane();
|
||||
announcementPane.setContent(announcementBox, ContainerAnimations.NONE);
|
||||
BorderPane titleBar = new BorderPane();
|
||||
titleBar.getStyleClass().add("title");
|
||||
titleBar.setLeft(new Label(title));
|
||||
|
||||
getChildren().add(announcementPane);
|
||||
}
|
||||
Node hideNode = SVG.CLOSE.createIcon(Theme.blackFill(), 20, 20);
|
||||
hideNode.setCursor(Cursor.HAND);
|
||||
titleBar.setRight(hideNode);
|
||||
FXUtils.onClicked(hideNode, () -> {
|
||||
if (announcementPane != null) {
|
||||
if (Metadata.isDev()) {
|
||||
config().getShownTips().put(ANNOUNCEMENT, Metadata.VERSION);
|
||||
}
|
||||
|
||||
announcementPane.setContent(new StackPane(), ContainerAnimations.FADE);
|
||||
}
|
||||
});
|
||||
|
||||
TextFlow body = FXUtils.segmentToTextFlow(content, Controllers::onHyperlinkAction);
|
||||
body.setLineSpacing(4);
|
||||
|
||||
announcementCard.getChildren().setAll(titleBar, body);
|
||||
announcementCard.setSpacing(16);
|
||||
announcementCard.getStyleClass().addAll("card", "announcement");
|
||||
|
||||
VBox announcementBox = new VBox(16);
|
||||
announcementBox.getChildren().add(announcementCard);
|
||||
|
||||
announcementPane = new TransitionPane();
|
||||
announcementPane.setContent(announcementBox, ContainerAnimations.NONE);
|
||||
|
||||
getChildren().add(announcementPane);
|
||||
}
|
||||
|
||||
updatePane = new StackPane();
|
||||
@@ -297,13 +325,6 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
showUpdate.set(false);
|
||||
}
|
||||
|
||||
public void hideAnnouncementPane() {
|
||||
if (announcementPane != null) {
|
||||
config().getShownTips().put(ANNOUNCEMENT, Metadata.VERSION);
|
||||
announcementPane.setContent(new StackPane(), ContainerAnimations.FADE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadOnlyObjectWrapper<State> stateProperty() {
|
||||
return state;
|
||||
|
||||
@@ -106,11 +106,13 @@ public class PersonalizationPage extends StackPane {
|
||||
backgroundSublist.setHasSubtitle(true);
|
||||
|
||||
backgroundItem.loadChildren(Arrays.asList(
|
||||
new MultiFileItem.Option<>(i18n("launcher.background.default"), EnumBackgroundImage.DEFAULT),
|
||||
new MultiFileItem.Option<>(i18n("launcher.background.default"), EnumBackgroundImage.DEFAULT)
|
||||
.setTooltip(i18n("launcher.background.default.tooltip")),
|
||||
new MultiFileItem.Option<>(i18n("launcher.background.classic"), EnumBackgroundImage.CLASSIC),
|
||||
new MultiFileItem.Option<>(i18n("launcher.background.translucent"), EnumBackgroundImage.TRANSLUCENT),
|
||||
new MultiFileItem.FileOption<>(i18n("settings.custom"), EnumBackgroundImage.CUSTOM)
|
||||
.setChooserTitle(i18n("launcher.background.choose"))
|
||||
.addExtensionFilter(FXUtils.getImageExtensionFilter())
|
||||
.bindBidirectional(config().backgroundImageProperty()),
|
||||
new MultiFileItem.StringOption<>(i18n("launcher.background.network"), EnumBackgroundImage.NETWORK)
|
||||
.setValidators(new URLValidator(true))
|
||||
|
||||
@@ -39,7 +39,7 @@ import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.nbt.NBTEditorPage;
|
||||
import org.jackhuang.hmcl.ui.nbt.NBTHelper;
|
||||
import org.jackhuang.hmcl.ui.nbt.NBTFileType;
|
||||
import org.jackhuang.hmcl.ui.versions.GameAdvancedListItem;
|
||||
import org.jackhuang.hmcl.ui.versions.Versions;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
@@ -90,16 +90,16 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
if (mainPage == null) {
|
||||
MainPage mainPage = new MainPage();
|
||||
FXUtils.applyDragListener(mainPage,
|
||||
file -> ModpackHelper.isFileModpackByExtension(file) || NBTHelper.isNBTFileByExtension(file),
|
||||
file -> ModpackHelper.isFileModpackByExtension(file) || NBTFileType.isNBTFileByExtension(file.toPath()),
|
||||
modpacks -> {
|
||||
File file = modpacks.get(0);
|
||||
if (ModpackHelper.isFileModpackByExtension(file)) {
|
||||
Controllers.getDecorator().startWizard(
|
||||
new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), file),
|
||||
i18n("install.modpack"));
|
||||
} else if (NBTHelper.isNBTFileByExtension(file)) {
|
||||
} else if (NBTFileType.isNBTFileByExtension(file.toPath())) {
|
||||
try {
|
||||
Controllers.navigate(new NBTEditorPage(file));
|
||||
Controllers.navigate(new NBTEditorPage(file.toPath()));
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Fail to open nbt file", e);
|
||||
Controllers.dialog(i18n("nbt.open.failed") + "\n\n" + StringUtils.getStackTrace(e),
|
||||
|
||||
@@ -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.ui.nbt;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
@@ -5,33 +22,38 @@ import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.ProgressIndicator;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.PageCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class NBTEditorPage extends BorderPane implements DecoratorPage {
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class NBTEditorPage extends SpinnerPane implements DecoratorPage {
|
||||
private final ReadOnlyObjectWrapper<State> state;
|
||||
private final File file;
|
||||
private final Path file;
|
||||
private final NBTFileType type;
|
||||
|
||||
public NBTEditorPage(File file) throws IOException {
|
||||
private final BorderPane root = new BorderPane();
|
||||
|
||||
public NBTEditorPage(Path file) throws IOException {
|
||||
getStyleClass().add("gray-background");
|
||||
|
||||
this.state = new ReadOnlyObjectWrapper<>(DecoratorPage.State.fromTitle(i18n("nbt.title", file.getAbsolutePath())));
|
||||
this.state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("nbt.title", file.toString())));
|
||||
this.file = file;
|
||||
this.type = NBTFileType.ofFile(file);
|
||||
|
||||
@@ -39,7 +61,8 @@ public class NBTEditorPage extends BorderPane implements DecoratorPage {
|
||||
throw new IOException("Unknown type of file " + file);
|
||||
}
|
||||
|
||||
setCenter(new ProgressIndicator());
|
||||
setContent(root);
|
||||
setLoading(true);
|
||||
|
||||
HBox actions = new HBox(8);
|
||||
actions.setPadding(new Insets(8));
|
||||
@@ -65,18 +88,16 @@ public class NBTEditorPage extends BorderPane implements DecoratorPage {
|
||||
|
||||
actions.getChildren().setAll(saveButton, cancelButton);
|
||||
|
||||
CompletableFuture.supplyAsync(Lang.wrap(() -> type.readAsTree(file)))
|
||||
.thenAcceptAsync(tree -> {
|
||||
setCenter(new NBTTreeView(tree));
|
||||
// setBottom(actions);
|
||||
}, Schedulers.javafx())
|
||||
.handleAsync((result, e) -> {
|
||||
if (e != null) {
|
||||
LOG.warning("Fail to open nbt file", e);
|
||||
Controllers.dialog(i18n("nbt.open.failed") + "\n\n" + StringUtils.getStackTrace(e), null, MessageDialogPane.MessageType.WARNING, cancelButton::fire);
|
||||
Task.supplyAsync(() -> type.readAsTree(file))
|
||||
.whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
if (exception == null) {
|
||||
setLoading(false);
|
||||
root.setCenter(new NBTTreeView(result));
|
||||
} else {
|
||||
LOG.warning("Fail to open nbt file", exception);
|
||||
Controllers.dialog(i18n("nbt.open.failed") + "\n\n" + StringUtils.getStackTrace(exception), null, MessageDialogPane.MessageType.WARNING, cancelButton::fire);
|
||||
}
|
||||
return null;
|
||||
}, Schedulers.javafx());
|
||||
}).start();
|
||||
}
|
||||
|
||||
public void save() throws IOException {
|
||||
|
||||
@@ -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.ui.nbt;
|
||||
|
||||
import com.github.steveice10.opennbt.NBTIO;
|
||||
@@ -9,16 +26,21 @@ import org.apache.commons.compress.utils.BoundedInputStream;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.Inflater;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public enum NBTFileType {
|
||||
COMPRESSED("dat", "dat_old") {
|
||||
@Override
|
||||
public Tag read(File file) throws IOException {
|
||||
try (BufferedInputStream fileInputStream = new BufferedInputStream(new FileInputStream(file))) {
|
||||
public Tag read(Path file) throws IOException {
|
||||
try (BufferedInputStream fileInputStream = new BufferedInputStream(Files.newInputStream(file))) {
|
||||
fileInputStream.mark(3);
|
||||
byte[] header = new byte[3];
|
||||
if (fileInputStream.read(header) < 3) {
|
||||
@@ -42,25 +64,28 @@ public enum NBTFileType {
|
||||
},
|
||||
ANVIL("mca") {
|
||||
@Override
|
||||
public Tag read(File file) throws IOException {
|
||||
public Tag read(Path file) throws IOException {
|
||||
return REGION.read(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NBTTreeView.Item readAsTree(File file) throws IOException {
|
||||
public NBTTreeView.Item readAsTree(Path file) throws IOException {
|
||||
return REGION.readAsTree(file);
|
||||
}
|
||||
},
|
||||
REGION("mcr") {
|
||||
@Override
|
||||
public Tag read(File file) throws IOException {
|
||||
try (RandomAccessFile r = new RandomAccessFile(file, "r")) {
|
||||
public Tag read(Path file) throws IOException {
|
||||
try (RandomAccessFile r = new RandomAccessFile(file.toFile(), "r")) {
|
||||
ListTag tag = new ListTag(file.getFileName().toString(), CompoundTag.class);
|
||||
if (r.length() == 0) {
|
||||
return tag;
|
||||
}
|
||||
|
||||
byte[] header = new byte[4096];
|
||||
byte[] buffer = new byte[1 * 1024 * 1024]; // The maximum size of each chunk is 1MiB
|
||||
Inflater inflater = new Inflater();
|
||||
|
||||
ListTag tag = new ListTag(file.getName(), CompoundTag.class);
|
||||
|
||||
r.readFully(header);
|
||||
for (int i = 0; i < 4096; i += 4) {
|
||||
int offset = ((header[i] & 0xff) << 16) + ((header[i + 1] & 0xff) << 8) + (header[i + 2] & 0xff);
|
||||
@@ -109,7 +134,7 @@ public enum NBTFileType {
|
||||
}
|
||||
|
||||
@Override
|
||||
public NBTTreeView.Item readAsTree(File file) throws IOException {
|
||||
public NBTTreeView.Item readAsTree(Path file) throws IOException {
|
||||
NBTTreeView.Item item = new NBTTreeView.Item(read(file));
|
||||
|
||||
for (Tag tag : ((ListTag) item.getValue())) {
|
||||
@@ -135,7 +160,11 @@ public enum NBTFileType {
|
||||
|
||||
static final NBTFileType[] types = values();
|
||||
|
||||
public static NBTFileType ofFile(File file) {
|
||||
public static boolean isNBTFileByExtension(Path file) {
|
||||
return NBTFileType.ofFile(file) != null;
|
||||
}
|
||||
|
||||
public static NBTFileType ofFile(Path file) {
|
||||
String ext = FileUtils.getExtension(file);
|
||||
for (NBTFileType type : types) {
|
||||
for (String extension : type.extensions) {
|
||||
@@ -153,11 +182,11 @@ public enum NBTFileType {
|
||||
this.extensions = extensions;
|
||||
}
|
||||
|
||||
public abstract Tag read(File file) throws IOException;
|
||||
public abstract Tag read(Path file) throws IOException;
|
||||
|
||||
public NBTTreeView.Item readAsTree(File file) throws IOException {
|
||||
public NBTTreeView.Item readAsTree(Path file) throws IOException {
|
||||
NBTTreeView.Item root = NBTTreeView.buildTree(read(file));
|
||||
root.setName(file.getName());
|
||||
root.setName(file.getFileName().toString());
|
||||
return root;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package org.jackhuang.hmcl.ui.nbt;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public final class NBTHelper {
|
||||
private NBTHelper() {
|
||||
}
|
||||
|
||||
public static boolean isNBTFileByExtension(File file) {
|
||||
return NBTFileType.ofFile(file) != null;
|
||||
}
|
||||
}
|
||||
@@ -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.ui.nbt;
|
||||
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
@@ -6,6 +23,9 @@ import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public enum NBTTagType {
|
||||
BYTE, SHORT, INT, LONG, FLOAT, DOUBLE,
|
||||
BYTE_ARRAY, INT_ARRAY, LONG_ARRAY,
|
||||
|
||||
@@ -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.ui.nbt;
|
||||
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
@@ -14,7 +31,10 @@ import java.util.EnumMap;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class NBTTreeView extends TreeView<Tag> {
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class NBTTreeView extends TreeView<Tag> {
|
||||
|
||||
public NBTTreeView(NBTTreeView.Item tree) {
|
||||
this.setRoot(tree);
|
||||
|
||||
@@ -240,7 +240,7 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
|
||||
FXUtils.unbind(txtWrapper, versionSetting.wrapperProperty());
|
||||
FXUtils.unbind(txtPreLaunchCommand, versionSetting.preLaunchCommandProperty());
|
||||
FXUtils.unbind(txtPostExitCommand, versionSetting.postExitCommandProperty());
|
||||
FXUtils.unbindEnum(cboRenderer);
|
||||
FXUtils.unbindEnum(cboRenderer, versionSetting.rendererProperty());
|
||||
noGameCheckPane.selectedProperty().unbindBidirectional(versionSetting.notCheckGameProperty());
|
||||
noJVMCheckPane.selectedProperty().unbindBidirectional(versionSetting.notCheckJVMProperty());
|
||||
noJVMArgsPane.selectedProperty().unbindBidirectional(versionSetting.noJVMArgsProperty());
|
||||
|
||||
@@ -27,6 +27,8 @@ import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
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;
|
||||
@@ -38,6 +40,7 @@ import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
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.util.i18n.I18n.i18n;
|
||||
|
||||
@@ -110,6 +113,9 @@ class DatapackListPageSkin extends SkinBase<DatapackListPage> {
|
||||
listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||
Bindings.bindContent(listView.getItems(), skinnable.getItems());
|
||||
|
||||
// ListViewBehavior would consume ESC pressed event, preventing us from handling it, so we ignore it here
|
||||
ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE);
|
||||
|
||||
center.setContent(listView);
|
||||
root.setCenter(center);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,8 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.*;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
@@ -64,6 +66,7 @@ import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.ignoreEvent;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.selectedItemPropertyFor;
|
||||
@@ -92,11 +95,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
private Runnable retrySearch;
|
||||
|
||||
public DownloadListPage(RemoteModRepository repository) {
|
||||
this(repository, null);
|
||||
}
|
||||
|
||||
public DownloadListPage(RemoteModRepository repository, DownloadPage.DownloadCallback callback) {
|
||||
this(repository, callback, false);
|
||||
this(repository, null, false);
|
||||
}
|
||||
|
||||
public DownloadListPage(RemoteModRepository repository, DownloadPage.DownloadCallback callback, boolean versionSelection) {
|
||||
@@ -493,6 +492,9 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
RemoteMod selectedItem = listView.getSelectionModel().getSelectedItem();
|
||||
Controllers.navigate(new DownloadPage(getSkinnable(), selectedItem, getSkinnable().getProfileVersion(), getSkinnable().callback));
|
||||
});
|
||||
|
||||
// ListViewBehavior would consume ESC pressed event, preventing us from handling it, so we ignore it here
|
||||
ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE);
|
||||
listView.setCellFactory(x -> new FloatListCell<RemoteMod>(listView) {
|
||||
TwoLineListItem content = new TwoLineListItem();
|
||||
ImageView imageView = new ImageView();
|
||||
|
||||
@@ -55,13 +55,11 @@ import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.FormatStyle;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class DownloadPage extends Control implements DecoratorPage {
|
||||
@@ -307,7 +305,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
|
||||
ComponentList sublist = new ComponentList(() -> {
|
||||
ArrayList<ModItem> items = new ArrayList<>(versions.size());
|
||||
for (RemoteMod.Version v: versions) {
|
||||
for (RemoteMod.Version v : versions) {
|
||||
items.add(new ModItem(v, control));
|
||||
}
|
||||
return items;
|
||||
@@ -380,13 +378,14 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
HBox descPane = new HBox(8);
|
||||
descPane.setPadding(new Insets(0, 8, 0, 8));
|
||||
descPane.setAlignment(Pos.CENTER_LEFT);
|
||||
descPane.setMouseTransparent(true);
|
||||
|
||||
{
|
||||
StackPane graphicPane = new StackPane();
|
||||
TwoLineListItem content = new TwoLineListItem();
|
||||
HBox.setHgrow(content, Priority.ALWAYS);
|
||||
content.setTitle(dataItem.getName());
|
||||
content.setSubtitle(FORMATTER.format(dataItem.getDatePublished()));
|
||||
content.setSubtitle(I18n.formatDateTime(dataItem.getDatePublished()));
|
||||
|
||||
switch (dataItem.getVersionType()) {
|
||||
case Alpha:
|
||||
@@ -440,12 +439,14 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
|
||||
private static final class ModVersion extends JFXDialogLayout {
|
||||
public ModVersion(RemoteMod.Version version, DownloadPage selfPage) {
|
||||
this.setHeading(new HBox(new Label(i18n("mods.download.title", version.getName()))));
|
||||
boolean isModpack = selfPage.repository.getType() == RemoteModRepository.Type.MODPACK;
|
||||
|
||||
this.setHeading(new HBox(new Label(i18n(isModpack ? "modpack.download.title" : "mods.download.title", version.getName()))));
|
||||
|
||||
VBox box = new VBox(8);
|
||||
box.setPadding(new Insets(8));
|
||||
ModItem modItem = new ModItem(version, selfPage);
|
||||
FXUtils.onClicked(modItem, () -> fireEvent(new DialogCloseEvent()));
|
||||
modItem.setMouseTransparent(true); // Item is displayed for info, clicking shouldn't open the dialog again
|
||||
box.getChildren().setAll(modItem);
|
||||
SpinnerPane spinnerPane = new SpinnerPane();
|
||||
ScrollPane scrollPane = new ScrollPane();
|
||||
@@ -462,10 +463,10 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
|
||||
this.setBody(box);
|
||||
|
||||
JFXButton downloadButton = new JFXButton(i18n("mods.install"));
|
||||
JFXButton downloadButton = new JFXButton(isModpack ? i18n("install.modpack") : i18n("mods.install"));
|
||||
downloadButton.getStyleClass().add("dialog-accept");
|
||||
downloadButton.setOnAction(e -> {
|
||||
if (!spinnerPane.isLoading() && spinnerPane.getFailedReason() == null) {
|
||||
if (isModpack || !spinnerPane.isLoading() && spinnerPane.getFailedReason() == null) {
|
||||
fireEvent(new DialogCloseEvent());
|
||||
}
|
||||
selfPage.download(version);
|
||||
@@ -488,6 +489,8 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
|
||||
this.prefWidthProperty().bind(BindingMapping.of(Controllers.getStage().widthProperty()).map(w -> w.doubleValue() * 0.7));
|
||||
this.prefHeightProperty().bind(BindingMapping.of(Controllers.getStage().heightProperty()).map(w -> w.doubleValue() * 0.7));
|
||||
|
||||
onEscPressed(this, cancelButton::fire);
|
||||
}
|
||||
|
||||
private void loadDependencies(RemoteMod.Version version, DownloadPage selfPage, SpinnerPane spinnerPane, ComponentList dependenciesList) {
|
||||
@@ -524,8 +527,6 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
}
|
||||
}
|
||||
|
||||
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL).withLocale(Locale.getDefault()).withZone(ZoneId.systemDefault());
|
||||
|
||||
public interface DownloadCallback {
|
||||
void download(Profile profile, @Nullable String version, RemoteMod.Version file);
|
||||
}
|
||||
|
||||
@@ -87,26 +87,10 @@ public class GameListPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
}
|
||||
|
||||
AdvancedListBox bottomLeftCornerList = new AdvancedListBox()
|
||||
.addNavigationDrawerItem(installNewGameItem -> {
|
||||
installNewGameItem.setTitle(i18n("install.new_game"));
|
||||
installNewGameItem.setLeftGraphic(VersionPage.wrap(SVG.PLUS_CIRCLE_OUTLINE));
|
||||
installNewGameItem.setOnAction(e -> Versions.addNewGame());
|
||||
})
|
||||
.addNavigationDrawerItem(installModpackItem -> {
|
||||
installModpackItem.setTitle(i18n("install.modpack"));
|
||||
installModpackItem.setLeftGraphic(VersionPage.wrap(SVG.PACK));
|
||||
installModpackItem.setOnAction(e -> Versions.importModpack());
|
||||
})
|
||||
.addNavigationDrawerItem(refreshItem -> {
|
||||
refreshItem.setTitle(i18n("button.refresh"));
|
||||
refreshItem.setLeftGraphic(VersionPage.wrap(SVG.REFRESH));
|
||||
refreshItem.setOnAction(e -> gameList.refreshList());
|
||||
})
|
||||
.addNavigationDrawerItem(globalManageItem -> {
|
||||
globalManageItem.setTitle(i18n("settings.type.global.manage"));
|
||||
globalManageItem.setLeftGraphic(VersionPage.wrap(SVG.GEAR_OUTLINE));
|
||||
globalManageItem.setOnAction(e -> modifyGlobalGameSettings());
|
||||
});
|
||||
.addNavigationDrawerItem(i18n("install.new_game"), SVG.PLUS_CIRCLE_OUTLINE, Versions::addNewGame)
|
||||
.addNavigationDrawerItem(i18n("install.modpack"), SVG.PACK, Versions::importModpack)
|
||||
.addNavigationDrawerItem(i18n("button.refresh"), SVG.REFRESH, gameList::refreshList)
|
||||
.addNavigationDrawerItem(i18n("settings.type.global.manage"), SVG.GEAR_OUTLINE, this::modifyGlobalGameSettings);
|
||||
FXUtils.setLimitHeight(bottomLeftCornerList, 40 * 4 + 12 * 2);
|
||||
setLeft(pane, bottomLeftCornerList);
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
})
|
||||
.whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
if (exception != null || result == null) {
|
||||
Controllers.dialog("Failed to check updates", "failed", MessageDialogPane.MessageType.ERROR);
|
||||
Controllers.dialog(i18n("mods.check_updates.failed_check"), i18n("message.failed"), MessageDialogPane.MessageType.ERROR);
|
||||
} else if (result.isEmpty()) {
|
||||
Controllers.dialog(i18n("mods.check_updates.empty"));
|
||||
} else {
|
||||
|
||||
@@ -31,6 +31,8 @@ import javafx.scene.control.SelectionMode;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.StackPane;
|
||||
@@ -70,6 +72,7 @@ import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.ignoreEvent;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
|
||||
import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2;
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
@@ -131,6 +134,8 @@ class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
Bindings.bindContent(listView.getItems(), getSkinnable().getItems());
|
||||
});
|
||||
|
||||
onEscPressed(searchField, closeSearchBar::fire);
|
||||
|
||||
searchBar.getChildren().setAll(searchField, closeSearchBar);
|
||||
|
||||
// Toolbar Normal
|
||||
@@ -168,6 +173,16 @@ class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
changeToolbar(toolbarSelecting);
|
||||
});
|
||||
root.getContent().add(toolbarPane);
|
||||
|
||||
// Clear selection when pressing ESC
|
||||
root.addEventHandler(KeyEvent.KEY_PRESSED, e -> {
|
||||
if (e.getCode() == KeyCode.ESCAPE) {
|
||||
if (listView.getSelectionModel().getSelectedItem() != null) {
|
||||
listView.getSelectionModel().clearSelection();
|
||||
e.consume();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
@@ -194,6 +209,10 @@ class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
}
|
||||
});
|
||||
|
||||
// ListViewBehavior would consume ESC pressed event, preventing us from handling it
|
||||
// So we ignore it here
|
||||
ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE);
|
||||
|
||||
center.setContent(listView);
|
||||
root.getContent().add(center);
|
||||
}
|
||||
@@ -379,6 +398,7 @@ class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
setHeading(titleContainer);
|
||||
|
||||
Label description = new Label(modInfo.getModInfo().getDescription().toString());
|
||||
FXUtils.copyOnDoubleClick(description);
|
||||
setBody(description);
|
||||
|
||||
if (StringUtils.isNotBlank(modInfo.getModInfo().getId())) {
|
||||
|
||||
@@ -139,7 +139,7 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
|
||||
task.whenComplete(Schedulers.javafx(), exception -> {
|
||||
fireEvent(new PageCloseEvent());
|
||||
if (!task.getFailedMods().isEmpty()) {
|
||||
Controllers.dialog(i18n("mods.check_updates.failed") + "\n" +
|
||||
Controllers.dialog(i18n("mods.check_updates.failed_download") + "\n" +
|
||||
task.getFailedMods().stream().map(LocalModFile::getFileName).collect(Collectors.joining("\n")),
|
||||
i18n("install.failed"),
|
||||
MessageDialogPane.MessageType.ERROR);
|
||||
|
||||
@@ -72,7 +72,7 @@ public class VersionIconDialog extends DialogPane {
|
||||
|
||||
private void exploreIcon() {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("extension.png"), "*.png", "*.jpg", "*.bmp", "*.gif"));
|
||||
chooser.getExtensionFilters().add(FXUtils.getImageExtensionFilter());
|
||||
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
|
||||
if (selectedFile != null) {
|
||||
try {
|
||||
|
||||
@@ -66,7 +66,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
|
||||
private String preferredVersionName = null;
|
||||
|
||||
{
|
||||
public VersionPage() {
|
||||
versionSettingsTab.setNodeSupplier(loadVersionFor(() -> new VersionSettingsPage(false)));
|
||||
installerListTab.setNodeSupplier(loadVersionFor(InstallerListPage::new));
|
||||
modListTab.setNodeSupplier(loadVersionFor(ModListPage::new));
|
||||
@@ -77,7 +77,8 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated);
|
||||
|
||||
tab.select(versionSettingsTab);
|
||||
FXUtils.onChangeAndOperate(tab.getSelectionModel().selectedItemProperty(), newValue -> {
|
||||
transitionPane.setContent(versionSettingsTab.getNode(), ContainerAnimations.NONE);
|
||||
FXUtils.onChange(tab.getSelectionModel().selectedItemProperty(), newValue -> {
|
||||
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE);
|
||||
});
|
||||
|
||||
@@ -308,25 +309,14 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
);
|
||||
|
||||
AdvancedListBox toolbar = new AdvancedListBox()
|
||||
.addNavigationDrawerItem(upgradeItem -> {
|
||||
upgradeItem.setTitle(i18n("version.update"));
|
||||
upgradeItem.setLeftGraphic(wrap(SVG.UPDATE));
|
||||
.addNavigationDrawerItem(i18n("version.update"), SVG.UPDATE, control::updateGame, upgradeItem -> {
|
||||
upgradeItem.visibleProperty().bind(control.currentVersionUpgradable);
|
||||
upgradeItem.setOnAction(e -> control.updateGame());
|
||||
})
|
||||
.addNavigationDrawerItem(testGameItem -> {
|
||||
testGameItem.setTitle(i18n("version.launch.test"));
|
||||
testGameItem.setLeftGraphic(wrap(SVG.ROCKET_LAUNCH_OUTLINE));
|
||||
testGameItem.setOnAction(e -> control.testGame());
|
||||
})
|
||||
.addNavigationDrawerItem(browseMenuItem -> {
|
||||
browseMenuItem.setTitle(i18n("settings.game.exploration"));
|
||||
browseMenuItem.setLeftGraphic(wrap(SVG.FOLDER_OUTLINE));
|
||||
.addNavigationDrawerItem(i18n("version.launch.test"), SVG.ROCKET_LAUNCH_OUTLINE, control::testGame)
|
||||
.addNavigationDrawerItem(i18n("settings.game.exploration"), SVG.FOLDER_OUTLINE, null, browseMenuItem -> {
|
||||
browseMenuItem.setOnAction(e -> browsePopup.show(browseMenuItem, JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.LEFT, browseMenuItem.getWidth(), 0));
|
||||
})
|
||||
.addNavigationDrawerItem(managementItem -> {
|
||||
managementItem.setTitle(i18n("settings.game.management"));
|
||||
managementItem.setLeftGraphic(wrap(SVG.WRENCH_OUTLINE));
|
||||
.addNavigationDrawerItem(i18n("settings.game.management"), SVG.WRENCH_OUTLINE, null, managementItem -> {
|
||||
managementItem.setOnAction(e -> managementPopup.show(managementItem, JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.LEFT, managementItem.getWidth(), 0));
|
||||
});
|
||||
toolbar.getStyleClass().add("advanced-list-box-clear-padding");
|
||||
|
||||
@@ -46,6 +46,7 @@ import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
import org.jackhuang.hmcl.util.javafx.PropertyUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
@@ -57,10 +58,28 @@ import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
|
||||
import static org.jackhuang.hmcl.util.Lang.getTimer;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class VersionSettingsPage extends StackPane implements DecoratorPage, VersionPage.VersionLoadable, PageAware {
|
||||
|
||||
private static final ObjectProperty<OperatingSystem.PhysicalMemoryStatus> memoryStatus = new SimpleObjectProperty<>(OperatingSystem.PhysicalMemoryStatus.INVALID);
|
||||
private static TimerTask memoryStatusUpdateTask;
|
||||
|
||||
private static void initMemoryStatusUpdateTask() {
|
||||
FXUtils.checkFxUserThread();
|
||||
if (memoryStatusUpdateTask != null)
|
||||
return;
|
||||
memoryStatusUpdateTask = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
Platform.runLater(() -> memoryStatus.set(OperatingSystem.getPhysicalMemoryStatus()));
|
||||
}
|
||||
};
|
||||
getTimer().scheduleAtFixedRate(memoryStatusUpdateTask, 0, 1000);
|
||||
}
|
||||
|
||||
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(new State("", null, false, false, false));
|
||||
|
||||
private AdvancedVersionSettingPage advancedVersionSettingPage;
|
||||
@@ -92,7 +111,8 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
private final ImagePickerItem iconPickerItem;
|
||||
|
||||
private final ChangeListener<Collection<JavaRuntime>> javaListChangeListener;
|
||||
private final InvalidationListener specificSettingsListener;
|
||||
private final InvalidationListener usesGlobalListener;
|
||||
private final ChangeListener<Boolean> specificSettingsListener;
|
||||
private final InvalidationListener javaListener = any -> initJavaSubtitle();
|
||||
private boolean updatingJavaSetting = false;
|
||||
private boolean updatingSelectedJava = false;
|
||||
@@ -101,7 +121,6 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
private final BooleanProperty navigateToSpecificSettings = new SimpleBooleanProperty(false);
|
||||
private final BooleanProperty enableSpecificSettings = new SimpleBooleanProperty(false);
|
||||
private final IntegerProperty maxMemory = new SimpleIntegerProperty();
|
||||
private final ObjectProperty<OperatingSystem.PhysicalMemoryStatus> memoryStatus = new SimpleObjectProperty<>(OperatingSystem.PhysicalMemoryStatus.INVALID);
|
||||
private final BooleanProperty modpack = new SimpleBooleanProperty();
|
||||
|
||||
public VersionSettingsPage(boolean globalSetting) {
|
||||
@@ -118,10 +137,6 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
rootPane.getStyleClass().add("card-list");
|
||||
|
||||
if (globalSetting) {
|
||||
HintPane skinHint = new HintPane(MessageDialogPane.MessageType.INFO);
|
||||
skinHint.setText(i18n("settings.skin"));
|
||||
rootPane.getChildren().add(skinHint);
|
||||
|
||||
HintPane specificSettingsHint = new HintPane(MessageDialogPane.MessageType.WARNING);
|
||||
Text text = new Text();
|
||||
text.textProperty().bind(BindingMapping.of(selectedVersion)
|
||||
@@ -173,6 +188,31 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
componentList = new ComponentList();
|
||||
componentList.setDepth(1);
|
||||
|
||||
if (!globalSetting) {
|
||||
BorderPane copyGlobalPane = new BorderPane();
|
||||
{
|
||||
Label label = new Label(i18n("settings.game.copy_global"));
|
||||
copyGlobalPane.setLeft(label);
|
||||
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
|
||||
|
||||
JFXButton copyAll = new JFXButton(i18n("settings.game.copy_global.copy_all"));
|
||||
copyAll.disableProperty().bind(modpack);
|
||||
copyGlobalPane.setRight(copyAll);
|
||||
copyAll.setOnAction(e -> Controllers.confirm(i18n("settings.game.copy_global.copy_all.confirm"), null, () -> {
|
||||
Set<String> ignored = new HashSet<>(Arrays.asList(
|
||||
"usesGlobal",
|
||||
"versionIcon"
|
||||
));
|
||||
|
||||
PropertyUtils.copyProperties(profile.getGlobal(), lastVersionSetting, name -> !ignored.contains(name));
|
||||
}, null));
|
||||
copyAll.getStyleClass().add("jfx-button-border");
|
||||
BorderPane.setAlignment(copyAll, Pos.CENTER_RIGHT);
|
||||
}
|
||||
|
||||
componentList.getContent().add(copyGlobalPane);
|
||||
}
|
||||
|
||||
javaItem = new MultiFileItem<>();
|
||||
javaSublist = new ComponentSublist();
|
||||
javaSublist.getContent().add(javaItem);
|
||||
@@ -185,7 +225,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
javaCustomOption = new MultiFileItem.FileOption<Pair<JavaVersionType, JavaRuntime>>(i18n("settings.custom"), pair(JavaVersionType.CUSTOM, null))
|
||||
.setChooserTitle(i18n("settings.game.java_directory.choose"));
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS)
|
||||
javaCustomOption.getExtensionFilters().add(new FileChooser.ExtensionFilter("Java", "java.exe"));
|
||||
javaCustomOption.addExtensionFilter(new FileChooser.ExtensionFilter("Java", "java.exe"));
|
||||
|
||||
javaListChangeListener = FXUtils.onWeakChangeAndOperate(JavaManager.getAllJavaProperty(), allJava -> {
|
||||
List<MultiFileItem.Option<Pair<JavaVersionType, JavaRuntime>>> options = new ArrayList<>();
|
||||
@@ -235,6 +275,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
VBox.setMargin(chkAutoAllocate, new Insets(0, 0, 8, 5));
|
||||
|
||||
HBox lowerBoundPane = new HBox(8);
|
||||
lowerBoundPane.setStyle("-fx-view-order: -1;"); // prevent the indicator from being covered by the progress bar
|
||||
lowerBoundPane.setAlignment(Pos.CENTER);
|
||||
VBox.setMargin(lowerBoundPane, new Insets(0, 0, 0, 16));
|
||||
{
|
||||
@@ -428,7 +469,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
showAdvancedSettingPane.setRight(button);
|
||||
}
|
||||
|
||||
componentList.getContent().setAll(
|
||||
componentList.getContent().addAll(
|
||||
javaSublist,
|
||||
gameDirSublist,
|
||||
maxMemoryPane,
|
||||
@@ -443,22 +484,8 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
|
||||
rootPane.getChildren().add(componentList);
|
||||
|
||||
initialize();
|
||||
|
||||
specificSettingsListener = any -> enableSpecificSettings.set(!lastVersionSetting.isUsesGlobal());
|
||||
|
||||
addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onDecoratorPageNavigating);
|
||||
|
||||
cboLauncherVisibility.getItems().setAll(LauncherVisibility.values());
|
||||
cboLauncherVisibility.setConverter(stringConverter(e -> i18n("settings.advanced.launcher_visibility." + e.name().toLowerCase(Locale.ROOT))));
|
||||
|
||||
cboProcessPriority.getItems().setAll(ProcessPriority.values());
|
||||
cboProcessPriority.setConverter(stringConverter(e -> i18n("settings.advanced.process_priority." + e.name().toLowerCase(Locale.ROOT))));
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
memoryStatus.set(OperatingSystem.getPhysicalMemoryStatus());
|
||||
enableSpecificSettings.addListener((a, b, newValue) -> {
|
||||
usesGlobalListener = any -> enableSpecificSettings.set(!lastVersionSetting.isUsesGlobal());
|
||||
specificSettingsListener = (a, b, newValue) -> {
|
||||
if (versionId == null) return;
|
||||
|
||||
// do not call versionSettings.setUsesGlobal(true/false)
|
||||
@@ -470,9 +497,20 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
profile.getRepository().globalizeVersionSetting(versionId);
|
||||
|
||||
Platform.runLater(() -> loadVersion(profile, versionId));
|
||||
});
|
||||
};
|
||||
|
||||
addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onDecoratorPageNavigating);
|
||||
|
||||
cboLauncherVisibility.getItems().setAll(LauncherVisibility.values());
|
||||
cboLauncherVisibility.setConverter(stringConverter(e -> i18n("settings.advanced.launcher_visibility." + e.name().toLowerCase(Locale.ROOT))));
|
||||
|
||||
cboProcessPriority.getItems().setAll(ProcessPriority.values());
|
||||
cboProcessPriority.setConverter(stringConverter(e -> i18n("settings.advanced.process_priority." + e.name().toLowerCase(Locale.ROOT))));
|
||||
|
||||
memoryStatus.set(OperatingSystem.getPhysicalMemoryStatus());
|
||||
componentList.disableProperty().bind(enableSpecificSettings.not());
|
||||
|
||||
initMemoryStatusUpdateTask();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -513,13 +551,13 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
javaCustomOption.valueProperty().unbindBidirectional(lastVersionSetting.javaDirProperty());
|
||||
gameDirCustomOption.valueProperty().unbindBidirectional(lastVersionSetting.gameDirProperty());
|
||||
FXUtils.unbind(txtServerIP, lastVersionSetting.serverIpProperty());
|
||||
FXUtils.unbindBoolean(chkAutoAllocate, lastVersionSetting.autoMemoryProperty());
|
||||
FXUtils.unbindBoolean(chkFullscreen, lastVersionSetting.fullscreenProperty());
|
||||
chkAutoAllocate.selectedProperty().unbindBidirectional(lastVersionSetting.autoMemoryProperty());
|
||||
chkFullscreen.selectedProperty().unbindBidirectional(lastVersionSetting.fullscreenProperty());
|
||||
showLogsPane.selectedProperty().unbindBidirectional(lastVersionSetting.showLogsProperty());
|
||||
FXUtils.unbindEnum(cboLauncherVisibility);
|
||||
FXUtils.unbindEnum(cboProcessPriority);
|
||||
FXUtils.unbindEnum(cboLauncherVisibility, lastVersionSetting.launcherVisibilityProperty());
|
||||
FXUtils.unbindEnum(cboProcessPriority, lastVersionSetting.processPriorityProperty());
|
||||
|
||||
lastVersionSetting.usesGlobalProperty().removeListener(specificSettingsListener);
|
||||
lastVersionSetting.usesGlobalProperty().removeListener(usesGlobalListener);
|
||||
lastVersionSetting.javaVersionTypeProperty().removeListener(javaListener);
|
||||
lastVersionSetting.javaDirProperty().removeListener(javaListener);
|
||||
lastVersionSetting.defaultJavaPathPropertyProperty().removeListener(javaListener);
|
||||
@@ -528,6 +566,8 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
gameDirItem.selectedDataProperty().unbindBidirectional(lastVersionSetting.gameDirTypeProperty());
|
||||
gameDirSublist.subtitleProperty().unbind();
|
||||
|
||||
enableSpecificSettings.removeListener(specificSettingsListener);
|
||||
|
||||
if (advancedVersionSettingPage != null) {
|
||||
advancedVersionSettingPage.unbindProperties();
|
||||
advancedVersionSettingPage = null;
|
||||
@@ -546,15 +586,16 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
javaCustomOption.bindBidirectional(versionSetting.javaDirProperty());
|
||||
gameDirCustomOption.bindBidirectional(versionSetting.gameDirProperty());
|
||||
FXUtils.bindString(txtServerIP, versionSetting.serverIpProperty());
|
||||
FXUtils.bindBoolean(chkAutoAllocate, versionSetting.autoMemoryProperty());
|
||||
FXUtils.bindBoolean(chkFullscreen, versionSetting.fullscreenProperty());
|
||||
chkAutoAllocate.selectedProperty().bindBidirectional(versionSetting.autoMemoryProperty());
|
||||
chkFullscreen.selectedProperty().bindBidirectional(versionSetting.fullscreenProperty());
|
||||
showLogsPane.selectedProperty().bindBidirectional(versionSetting.showLogsProperty());
|
||||
FXUtils.bindEnum(cboLauncherVisibility, versionSetting.launcherVisibilityProperty());
|
||||
FXUtils.bindEnum(cboProcessPriority, versionSetting.processPriorityProperty());
|
||||
|
||||
versionSetting.usesGlobalProperty().addListener(specificSettingsListener);
|
||||
if (versionId != null)
|
||||
enableSpecificSettings.set(!versionSetting.isUsesGlobal());
|
||||
versionSetting.usesGlobalProperty().addListener(usesGlobalListener);
|
||||
enableSpecificSettings.addListener(specificSettingsListener);
|
||||
|
||||
javaItem.setToggleSelectedListener(newValue -> {
|
||||
if (javaItem.getSelectedData() == null || updatingSelectedJava)
|
||||
|
||||
@@ -83,7 +83,7 @@ public final class Versions {
|
||||
} catch (IOException e) {
|
||||
Controllers.dialog(
|
||||
i18n("install.failed.downloading.detail", file.getFile().getUrl()) + "\n" + StringUtils.getStackTrace(e),
|
||||
i18n("download.failed.no_code", file.getFile().getUrl()), MessageDialogPane.MessageType.ERROR);
|
||||
i18n("download.failed.no_code"), MessageDialogPane.MessageType.ERROR);
|
||||
return;
|
||||
}
|
||||
Controllers.taskDialog(
|
||||
@@ -96,7 +96,7 @@ public final class Versions {
|
||||
} else {
|
||||
Controllers.dialog(
|
||||
i18n("install.failed.downloading.detail", file.getFile().getUrl()) + "\n" + StringUtils.getStackTrace(e),
|
||||
i18n("download.failed.no_code", file.getFile().getUrl()), MessageDialogPane.MessageType.ERROR);
|
||||
i18n("download.failed.no_code"), MessageDialogPane.MessageType.ERROR);
|
||||
}
|
||||
}).executor(true),
|
||||
i18n("message.downloading"),
|
||||
|
||||
@@ -103,6 +103,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
|
||||
|
||||
Label worldNameLabel = new Label();
|
||||
FXUtils.copyOnDoubleClick(worldNameLabel);
|
||||
worldNameLabel.setText(world.getWorldName());
|
||||
BorderPane.setAlignment(worldNameLabel, Pos.CENTER_RIGHT);
|
||||
worldNamePane.setRight(worldNameLabel);
|
||||
@@ -115,6 +116,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
gameVersionPane.setLeft(label);
|
||||
|
||||
Label gameVersionLabel = new Label();
|
||||
FXUtils.copyOnDoubleClick(gameVersionLabel);
|
||||
gameVersionLabel.setText(world.getGameVersion());
|
||||
BorderPane.setAlignment(gameVersionLabel, Pos.CENTER_RIGHT);
|
||||
gameVersionPane.setRight(gameVersionLabel);
|
||||
@@ -127,6 +129,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
randomSeedPane.setLeft(label);
|
||||
|
||||
Label randomSeedLabel = new Label();
|
||||
FXUtils.copyOnDoubleClick(randomSeedLabel);
|
||||
BorderPane.setAlignment(randomSeedLabel, Pos.CENTER_RIGHT);
|
||||
randomSeedPane.setRight(randomSeedLabel);
|
||||
|
||||
@@ -143,6 +146,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
lastPlayedPane.setLeft(label);
|
||||
|
||||
Label lastPlayedLabel = new Label();
|
||||
FXUtils.copyOnDoubleClick(lastPlayedLabel);
|
||||
lastPlayedLabel.setText(formatDateTime(Instant.ofEpochMilli(world.getLastPlayed())));
|
||||
BorderPane.setAlignment(lastPlayedLabel, Pos.CENTER_RIGHT);
|
||||
lastPlayedPane.setRight(lastPlayedLabel);
|
||||
@@ -155,6 +159,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
timePane.setLeft(label);
|
||||
|
||||
Label timeLabel = new Label();
|
||||
FXUtils.copyOnDoubleClick(timeLabel);
|
||||
BorderPane.setAlignment(timeLabel, Pos.CENTER_RIGHT);
|
||||
timePane.setRight(timeLabel);
|
||||
|
||||
@@ -258,6 +263,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
locationPane.setLeft(label);
|
||||
|
||||
Label locationLabel = new Label();
|
||||
FXUtils.copyOnDoubleClick(locationLabel);
|
||||
BorderPane.setAlignment(locationLabel, Pos.CENTER_RIGHT);
|
||||
locationPane.setRight(locationLabel);
|
||||
|
||||
@@ -276,6 +282,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
lastDeathLocationPane.setLeft(label);
|
||||
|
||||
Label lastDeathLocationLabel = new Label();
|
||||
FXUtils.copyOnDoubleClick(lastDeathLocationLabel);
|
||||
BorderPane.setAlignment(lastDeathLocationLabel, Pos.CENTER_RIGHT);
|
||||
lastDeathLocationPane.setRight(lastDeathLocationLabel);
|
||||
|
||||
@@ -297,6 +304,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
spawnPane.setLeft(label);
|
||||
|
||||
Label spawnLabel = new Label();
|
||||
FXUtils.copyOnDoubleClick(spawnLabel);
|
||||
BorderPane.setAlignment(spawnLabel, Pos.CENTER_RIGHT);
|
||||
spawnPane.setRight(spawnLabel);
|
||||
|
||||
|
||||
@@ -34,8 +34,8 @@ public final class Lazy<T> {
|
||||
}
|
||||
|
||||
public T get() {
|
||||
if (value == null) {
|
||||
value = Objects.requireNonNull(supplier.get());
|
||||
if (supplier != null) {
|
||||
value = supplier.get();
|
||||
supplier = null;
|
||||
}
|
||||
return value;
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
import org.jackhuang.hmcl.mod.ModManager;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
@@ -60,7 +62,11 @@ public final class NativePatcher {
|
||||
});
|
||||
}
|
||||
|
||||
public static Version patchNative(Version version, String gameVersion, JavaRuntime javaVersion, VersionSetting settings) {
|
||||
public static Version patchNative(DefaultGameRepository repository,
|
||||
Version version, String gameVersion,
|
||||
JavaRuntime javaVersion,
|
||||
VersionSetting settings,
|
||||
List<String> javaArguments) {
|
||||
if (settings.getNativesDirType() == NativesDirectoryType.CUSTOM) {
|
||||
if (gameVersion != null && GameVersionNumber.compare(gameVersion, "1.19") < 0)
|
||||
return version;
|
||||
@@ -83,7 +89,7 @@ public final class NativePatcher {
|
||||
final boolean useNativeOpenAL = settings.isUseNativeOpenAL();
|
||||
|
||||
if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() && (useNativeGLFW || useNativeOpenAL)
|
||||
&& GameVersionNumber.compare(gameVersion, "1.19") >= 0) {
|
||||
&& gameVersion != null && GameVersionNumber.compare(gameVersion, "1.19") >= 0) {
|
||||
|
||||
version = version.setLibraries(version.getLibraries().stream()
|
||||
.filter(library -> {
|
||||
@@ -124,6 +130,7 @@ public final class NativePatcher {
|
||||
return version;
|
||||
}
|
||||
|
||||
boolean lwjglVersionChanged = false;
|
||||
ArrayList<Library> newLibraries = new ArrayList<>();
|
||||
for (Library library : version.getLibraries()) {
|
||||
if (!library.appliesToCurrentEnvironment())
|
||||
@@ -145,14 +152,33 @@ public final class NativePatcher {
|
||||
} else if (replacement != null) {
|
||||
LOG.info("Replace " + library.getName() + " with " + replacement.getName());
|
||||
newLibraries.add(replacement);
|
||||
|
||||
if ("org.lwjgl:lwjgl".equals(library.getName()) && !Objects.equals(library.getVersion(), replacement.getVersion())) {
|
||||
lwjglVersionChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lwjglVersionChanged) {
|
||||
ModManager modManager = repository.getModManager(version.getId());
|
||||
try {
|
||||
for (LocalModFile mod : modManager.getMods()) {
|
||||
if ("sodium".equals(mod.getId())) {
|
||||
// https://github.com/CaffeineMC/sodium/issues/2561
|
||||
javaArguments.add("-Dsodium.checks.issue2561=false");
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Failed to get mods", e);
|
||||
}
|
||||
}
|
||||
|
||||
return version.setLibraries(newLibraries);
|
||||
}
|
||||
|
||||
public static Library getMesaLoader(JavaRuntime javaVersion, Renderer renderer) {
|
||||
return getNatives(javaVersion.getPlatform()).get(renderer == Renderer.LLVMPIPE ? "software-renderer-loader" : "mesa-loader");
|
||||
public static Library getMesaLoader(JavaRuntime javaVersion) {
|
||||
return getNatives(javaVersion.getPlatform()).get("mesa-loader");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
// Copy from javafx.swing
|
||||
/*
|
||||
* Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code 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
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import javafx.scene.image.*;
|
||||
import javafx.scene.image.Image;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBufferInt;
|
||||
import java.awt.image.SampleModel;
|
||||
import java.awt.image.SinglePixelPackedSampleModel;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
/**
|
||||
* This class provides utility methods for converting data types between
|
||||
* Swing/AWT and JavaFX formats.
|
||||
*
|
||||
* @since JavaFX 2.2
|
||||
*/
|
||||
public final class SwingFXUtils {
|
||||
private SwingFXUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Snapshots the specified {@link BufferedImage} and stores a copy of
|
||||
* its pixels into a JavaFX {@link Image} object, creating a new
|
||||
* object if needed.
|
||||
* The returned {@code Image} will be a static snapshot of the state
|
||||
* of the pixels in the {@code BufferedImage} at the time the method
|
||||
* completes. Further changes to the {@code BufferedImage} will not
|
||||
* be reflected in the {@code Image}.
|
||||
* <p>
|
||||
* The optional JavaFX {@link WritableImage} parameter may be reused
|
||||
* to store the copy of the pixels.
|
||||
* A new {@code Image} will be created if the supplied object is null,
|
||||
* is too small or of a type which the image pixels cannot be easily
|
||||
* converted into.
|
||||
*
|
||||
* @param bimg the {@code BufferedImage} object to be converted
|
||||
* @param wimg an optional {@code WritableImage} object that can be
|
||||
* used to store the returned pixel data
|
||||
* @return an {@code Image} object representing a snapshot of the
|
||||
* current pixels in the {@code BufferedImage}.
|
||||
* @since JavaFX 2.2
|
||||
*/
|
||||
public static WritableImage toFXImage(BufferedImage bimg, WritableImage wimg) {
|
||||
int bw = bimg.getWidth();
|
||||
int bh = bimg.getHeight();
|
||||
switch (bimg.getType()) {
|
||||
case BufferedImage.TYPE_INT_ARGB:
|
||||
case BufferedImage.TYPE_INT_ARGB_PRE:
|
||||
break;
|
||||
default:
|
||||
BufferedImage converted =
|
||||
new BufferedImage(bw, bh, BufferedImage.TYPE_INT_ARGB_PRE);
|
||||
Graphics2D g2d = converted.createGraphics();
|
||||
g2d.drawImage(bimg, 0, 0, null);
|
||||
g2d.dispose();
|
||||
bimg = converted;
|
||||
break;
|
||||
}
|
||||
// assert(bimg.getType == TYPE_INT_ARGB[_PRE]);
|
||||
if (wimg != null) {
|
||||
int iw = (int) wimg.getWidth();
|
||||
int ih = (int) wimg.getHeight();
|
||||
if (iw < bw || ih < bh) {
|
||||
wimg = null;
|
||||
} else if (bw < iw || bh < ih) {
|
||||
int[] empty = new int[iw];
|
||||
PixelWriter pw = wimg.getPixelWriter();
|
||||
PixelFormat<IntBuffer> pf = PixelFormat.getIntArgbPreInstance();
|
||||
if (bw < iw) {
|
||||
pw.setPixels(bw, 0, iw - bw, bh, pf, empty, 0, 0);
|
||||
}
|
||||
if (bh < ih) {
|
||||
pw.setPixels(0, bh, iw, ih - bh, pf, empty, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (wimg == null) {
|
||||
wimg = new WritableImage(bw, bh);
|
||||
}
|
||||
PixelWriter pw = wimg.getPixelWriter();
|
||||
DataBufferInt db = (DataBufferInt) bimg.getRaster().getDataBuffer();
|
||||
int[] data = db.getData();
|
||||
int offset = bimg.getRaster().getDataBuffer().getOffset();
|
||||
int scan = 0;
|
||||
SampleModel sm = bimg.getRaster().getSampleModel();
|
||||
if (sm instanceof SinglePixelPackedSampleModel) {
|
||||
scan = ((SinglePixelPackedSampleModel) sm).getScanlineStride();
|
||||
}
|
||||
|
||||
PixelFormat<IntBuffer> pf = (bimg.isAlphaPremultiplied() ?
|
||||
PixelFormat.getIntArgbPreInstance() :
|
||||
PixelFormat.getIntArgbInstance());
|
||||
pw.setPixels(0, 0, bw, bh, pf, data, offset, scan);
|
||||
return wimg;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ public final class I18n {
|
||||
public static String formatDateTime(Instant instant) {
|
||||
DateTimeFormatter formatter = dateTimeFormatter;
|
||||
if (formatter == null) {
|
||||
formatter = dateTimeFormatter = DateTimeFormatter.ofPattern(getResourceBundle().getString("world.time")).withZone(ZoneId.systemDefault());
|
||||
formatter = dateTimeFormatter = DateTimeFormatter.ofPattern(getResourceBundle().getString("datetime.format")).withZone(ZoneId.systemDefault());
|
||||
}
|
||||
|
||||
return formatter.format(instant);
|
||||
|
||||
@@ -33,6 +33,8 @@ case "$(uname -m)" in
|
||||
_HMCL_ARCH="arm64";;
|
||||
arm|arm32|aarch32|armv7*)
|
||||
_HMCL_ARCH="arm32";;
|
||||
riscv64)
|
||||
_HMCL_ARCH="riscv64";;
|
||||
loongarch64)
|
||||
_HMCL_ARCH="loongarch64";;
|
||||
*)
|
||||
@@ -56,17 +58,42 @@ else
|
||||
_HMCL_VM_OPTIONS="-XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=15"
|
||||
fi
|
||||
|
||||
function show_warning_console() {
|
||||
echo -e "\033[1;31m$1\033[0m" >&2
|
||||
}
|
||||
|
||||
function show_warning_dialog() {
|
||||
if [ -n "$3" ]; then
|
||||
if [ -n "$(command -v xdg-open)" ]; then
|
||||
if [ -n "$(command -v zenity)" ]; then
|
||||
zenity --question --title="$1" --text="$2" && xdg-open "$3"
|
||||
elif [ -n "$(command -v kdialog)" ]; then
|
||||
kdialog --title "$1" --warningyesno "$2" && xdg-open "$3"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
if [ -n "$(command -v zenity)" ]; then
|
||||
zenity --info --title="$1" --text="$2"
|
||||
elif [ -n "$(command -v kdialog)" ]; then
|
||||
kdialog --title "$1" --msgbox "$2"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
function show_warning() {
|
||||
show_warning_console "$1: $2"
|
||||
show_warning_dialog "$1" "$2"
|
||||
}
|
||||
|
||||
# First, find Java in HMCL_JAVA_HOME
|
||||
if [ -n "${HMCL_JAVA_HOME+x}" ]; then
|
||||
if [ -x "$HMCL_JAVA_HOME/bin/$_HMCL_JAVA_EXE_NAME" ]; then
|
||||
exec "$HMCL_JAVA_HOME/bin/$_HMCL_JAVA_EXE_NAME" $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH"
|
||||
else
|
||||
if [ "$_HMCL_USE_CHINESE" == true ]; then
|
||||
echo "环境变量 HMCL_JAVA_HOME 的值无效,请设置为合法的 Java 路径。" 1>&2
|
||||
echo "你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。" 1>&2
|
||||
show_warning "错误" "环境变量 HMCL_JAVA_HOME 的值无效,请设置为合法的 Java 路径。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。"
|
||||
else
|
||||
echo "The value of the environment variable HMCL_JAVA_HOME is invalid, please set it to a valid Java path." 1>&2
|
||||
echo "You can visit the https://docs.hmcl.net/help.html page for help." 1>&2
|
||||
show_warning "Error" "The value of the environment variable HMCL_JAVA_HOME is invalid, please set it to a valid Java path.\nYou can visit the https://docs.hmcl.net/help.html page for help."
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
@@ -97,6 +124,11 @@ case "$_HMCL_ARCH" in
|
||||
exec "$_HMCL_DIR/jre-arm32/bin/$_HMCL_JAVA_EXE_NAME" $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH"
|
||||
fi
|
||||
;;
|
||||
riscv64)
|
||||
if [ -x "$_HMCL_DIR/jre-riscv64/bin/$_HMCL_JAVA_EXE_NAME" ]; then
|
||||
exec "$_HMCL_DIR/jre-riscv64/bin/$_HMCL_JAVA_EXE_NAME" $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH"
|
||||
fi
|
||||
;;
|
||||
loongarch64)
|
||||
if [ -x "$_HMCL_DIR/jre-loongarch64/bin/$_HMCL_JAVA_EXE_NAME" ]; then
|
||||
exec "$_HMCL_DIR/jre-loongarch64/bin/$_HMCL_JAVA_EXE_NAME" $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH"
|
||||
@@ -114,37 +146,41 @@ if [ -x "$(command -v $_HMCL_JAVA_EXE_NAME)" ]; then
|
||||
exec $_HMCL_JAVA_EXE_NAME $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH"
|
||||
fi
|
||||
|
||||
# Java not found
|
||||
|
||||
if [ "$_HMCL_OS" == "osx" ]; then
|
||||
_HMCL_DOWNLOAD_PAGE_OS="macos"
|
||||
else
|
||||
_HMCL_DOWNLOAD_PAGE_OS="$_HMCL_OS"
|
||||
if [ "$_HMCL_OS" == "freebsd" ] && [ -x "$(command -v javavm)" ]; then
|
||||
exec javavm $_HMCL_VM_OPTIONS -jar "$_HMCL_PATH"
|
||||
fi
|
||||
|
||||
# Java not found
|
||||
|
||||
case "$_HMCL_OS-$_HMCL_ARCH" in
|
||||
windows-x86|windows-x86_64|windows-arm64|linux-x86|linux-x86_64|linux-arm32|linux-arm64|linux-loongarch64|macos-x86_64|macos-arm64)
|
||||
if [ "$_HMCL_USE_CHINESE" == true ]; then
|
||||
echo "运行 HMCL 需要 Java 运行时环境,请安装 Java 并设置环境变量后重试。" 1>&2
|
||||
echo "https://docs.hmcl.net/downloads/$_HMCL_DOWNLOAD_PAGE_OS/$_HMCL_HMCL_ARCH.html" 1>&2
|
||||
echo "你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。" 1>&2
|
||||
else
|
||||
echo "The Java runtime environment is required to run HMCL. " 1>&2
|
||||
echo "Please install Java and set the environment variables and try again." 1>&2
|
||||
echo "https://docs.hmcl.net/downloads/$_HMCL_DOWNLOAD_PAGE_OS/$_HMCL_HMCL_ARCH.html" 1>&2
|
||||
echo "You can visit the https://docs.hmcl.net/help.html page for help." 1>&2
|
||||
fi
|
||||
windows-x86|windows-x86_64|windows-arm64)
|
||||
_HMCL_JAVA_DOWNLOAD_PAGE="https://docs.hmcl.net/downloads/windows/$_HMCL_ARCH.html"
|
||||
;;
|
||||
*)
|
||||
if [ "$_HMCL_USE_CHINESE" == true ]; then
|
||||
echo "运行 HMCL 需要 Java 运行时环境,请安装 Java 并设置环境变量后重试。" 1>&2
|
||||
echo "你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。" 1>&2
|
||||
else
|
||||
echo "The Java runtime environment is required to run HMCL. " 1>&2
|
||||
echo "Please install Java and set the environment variables and try again." 1>&2
|
||||
echo "You can visit the https://docs.hmcl.net/help.html page for help." 1>&2
|
||||
fi
|
||||
linux-x86|linux-x86_64|linux-arm64|linux-arm32|linux-riscv64|linux-loongarch64)
|
||||
_HMCL_JAVA_DOWNLOAD_PAGE="https://docs.hmcl.net/downloads/linux/$_HMCL_ARCH.html"
|
||||
;;
|
||||
osx-x86_64|osx-arm64)
|
||||
_HMCL_JAVA_DOWNLOAD_PAGE="https://docs.hmcl.net/downloads/macos/$_HMCL_ARCH.html"
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$_HMCL_USE_CHINESE" == true ]; then
|
||||
_HMCL_WARNING_MESSAGE="运行 HMCL 需要 Java 运行时环境,请安装 Java 并设置环境变量后重试。"
|
||||
|
||||
if [ -n "$_HMCL_JAVA_DOWNLOAD_PAGE" ]; then
|
||||
show_warning_console "错误: $_HMCL_WARNING_MESSAGE\n你可以前往此处下载:\n$_HMCL_JAVA_DOWNLOAD_PAGE"
|
||||
show_warning_dialog "错误" "$_HMCL_WARNING_MESSAGE\n\n是否要前往 Java 下载页面?" "$_HMCL_JAVA_DOWNLOAD_PAGE"
|
||||
else
|
||||
show_warning "错误" "$_HMCL_WARNING_MESSAGE\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。"
|
||||
fi
|
||||
else
|
||||
_HMCL_WARNING_MESSAGE="The Java runtime environment is required to run HMCL.\nPlease install Java and set the environment variables and try again."
|
||||
if [ -n "$_HMCL_JAVA_DOWNLOAD_PAGE" ]; then
|
||||
show_warning_console "Error: $_HMCL_WARNING_MESSAGE\nYou can download it from here:\n$_HMCL_JAVA_DOWNLOAD_PAGE"
|
||||
show_warning_dialog "Error" "$_HMCL_WARNING_MESSAGE\n\nDo you want to go to the Java download page?" "$_HMCL_JAVA_DOWNLOAD_PAGE"
|
||||
else
|
||||
show_warning "Error" "$_HMCL_WARNING_MESSAGE\nYou can visit the https://docs.hmcl.net/help.html page for help."
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 1
|
||||
@@ -1307,11 +1307,6 @@
|
||||
-fx-padding: 8;
|
||||
}
|
||||
|
||||
.body {
|
||||
-fx-border-radius: 5;
|
||||
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.4), 10, 0.3, 0.0, 0.0);
|
||||
}
|
||||
|
||||
.debug-border {
|
||||
-fx-border-color: red;
|
||||
-fx-border-width: 1;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
# 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
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
about=About
|
||||
about.copyright=Copyright
|
||||
about.copyright.statement=Copyright © 2024 huangyuhui
|
||||
about.copyright.statement=Copyright © 2025 huangyuhui
|
||||
about.author=Author
|
||||
about.author.statement=bilibili @huanghongxun
|
||||
about.claim=EULA
|
||||
@@ -66,17 +66,17 @@ account.failed.injector_download_failure=Failed to download the authlib-injector
|
||||
account.failed.invalid_credentials=Incorrect password or rate limited. Please try again later.
|
||||
account.failed.invalid_password=Invalid password.
|
||||
account.failed.invalid_token=Please try logging in again.
|
||||
account.failed.migration=Your account needs to be migrated to a Microsoft account. If you already did, you should log in to your migrated Microsoft account again.
|
||||
account.failed.migration=Your account needs to be migrated to a Microsoft account. If you already did, you should log in using your migrated Microsoft account again.
|
||||
account.failed.no_character=There are no characters linked to this account.
|
||||
account.failed.server_disconnected=Failed to connect to the authentication server. You can log in using offline mode or try logging in again.\n\
|
||||
If you try several times and still fail, please try logging in to the account again.
|
||||
account.failed.server_response_malformed=Invalid server response. The authentication server may not be working.
|
||||
If the issue persists after multiple attempts, please try logging in to the account again.
|
||||
account.failed.server_response_malformed=Invalid server response. The authentication server may be down.
|
||||
account.failed.ssl=An SSL error occurred while connecting to the server. Please try updating your Java.
|
||||
account.failed.wrong_account=You have logged in to the wrong account.
|
||||
account.hmcl.hint=You need to click "Login" and complete the process in the opened browser window.
|
||||
account.hmcl.hint=You need to click "Log in" and complete the process in the opened browser window.
|
||||
account.injector.add=New Auth Server
|
||||
account.injector.empty=None (You can click "+" to add one)
|
||||
account.injector.http=Warning: This server uses the unsafe HTTP protocol. Anyone between your connections will be able to see your credentials in cleartext.
|
||||
account.injector.http=Warning: This server uses the unsafe HTTP protocol. Anyone intercepting your connection would be able to see your credentials in plaintext.
|
||||
account.injector.link.homepage=Homepage
|
||||
account.injector.link.register=Register
|
||||
account.injector.server=Authentication Server
|
||||
@@ -95,16 +95,16 @@ account.copy_uuid=Copy UUID of the Account
|
||||
account.methods=Login Type
|
||||
account.methods.authlib_injector=authlib-injector
|
||||
account.methods.microsoft=Microsoft
|
||||
account.methods.microsoft.birth=How to Change Your Account Birthday
|
||||
account.methods.microsoft.birth=How to Change Your Account Birth Date
|
||||
account.methods.microsoft.close_page=Microsoft account authorization is now completed.\n\
|
||||
\n\
|
||||
There are some extra works for us, but you can safely close this tab for now.
|
||||
account.methods.microsoft.deauthorize=Deauthorize
|
||||
account.methods.microsoft.error.add_family=An adult must add you to a family in order for you to play Minecraft because you are not yet 18 years old.
|
||||
account.methods.microsoft.error.add_family_probably=Please check if the age indicated in your account settings is at least 18 years old. If not and you believe this is an error, you can click "How to Change Your Account Birthday" to learn how to change it.
|
||||
account.methods.microsoft.error.add_family_probably=Please check if the age indicated in your account settings is at least 18 years old. If not and you believe this is an error, you can click "How to Change Your Account Birth Date" to learn how to change it.
|
||||
account.methods.microsoft.error.country_unavailable=Xbox Live is not available in your current country/region.
|
||||
account.methods.microsoft.error.missing_xbox_account=Your Microsoft account does not have a linked Xbox account yet. Please click "Create Profile / Edit Profile Name" to create one before continuing.
|
||||
account.methods.microsoft.error.no_character=Your account does not own the Minecraft Java Edition.\nThe game profile may not have been created\nplease click "Create Profile / Edit Profile Name" to create it.
|
||||
account.methods.microsoft.error.no_character=Please ensure you have purchased Minecraft: Java Edition.\nIf you have already purchased the game, a profile may not have been created yet.\nClick "Create Profile / Edit Profile Name" to create it.
|
||||
account.methods.microsoft.error.unknown=Failed to log in, error code: %d.
|
||||
account.methods.microsoft.error.wrong_verify_method=Please log in using your password on the Microsoft account login page, and do not use a verification code to log in.
|
||||
account.methods.microsoft.logging_in=Logging in...
|
||||
@@ -113,26 +113,26 @@ account.methods.microsoft.hint=Please click "Log in" and copy the code displayed
|
||||
If the token used to log in to the Microsoft account is leaked, you can click "Deauthorize" to deauthorize it.
|
||||
account.methods.microsoft.manual=Your device code is <b>%1$s</b>. Please click here to copy.\n\
|
||||
\n\
|
||||
After clicking "Log in", you should complete the login process in the opened browser window. If it did not show, you can navigate to %2$s manually.\n\
|
||||
After clicking "Log in", you should complete the login process in the opened browser window. If that does not show up, you can navigate to %2$s manually.\n\
|
||||
\n\
|
||||
If the token used to log in to the Microsoft account is leaked, you can click "Deauthorize" to deauthorize it.
|
||||
account.methods.microsoft.makegameidsettings=Create Profile / Edit Profile Name
|
||||
account.methods.microsoft.profile=Account Profile
|
||||
account.methods.microsoft.purchase=Buy Minecraft
|
||||
account.methods.microsoft.snapshot=You are using an unofficial build of HMCL. Please download the official build for login.
|
||||
account.methods.microsoft.snapshot=You are using an unofficial build of HMCL. Please download the official build to log in.
|
||||
account.methods.microsoft.snapshot.website=Official Website
|
||||
account.methods.offline=Offline
|
||||
account.methods.offline.name.special_characters=Recommended to use English letters, numbers, and underscores
|
||||
account.methods.offline.name.invalid=Generally, game usernames only allow English letters, numbers, and underscores and cannot exceed 16 characters in length.\n\
|
||||
account.methods.offline.name.special_characters=Using only English letters, numbers, and underscores is recommended
|
||||
account.methods.offline.name.invalid=Generally, game usernames are limited to English letters, numbers, and underscores and cannot exceed 16 characters in length.\n\
|
||||
\n\
|
||||
\ · Some legitimate usernames: HuangYu, huang_Yu, Huang_Yu_123;\n\
|
||||
\ · Some illegal usernames: Huang Yu, Huang-Yu_%%%, Huang_Yu_hello_world_hello_world.\n\
|
||||
\ · Valid usernames: HuangYu, huang_Yu, Huang_Yu_123;\n\
|
||||
\ · Invalid usernames: Huang Yu, Huang-Yu_%%%, Huang_Yu_hello_world_hello_world.\n\
|
||||
\n\
|
||||
If you believe that there is a corresponding mod or plugin on the server side to remove this restriction, you can ignore this warning.
|
||||
If you believe there is a corresponding mod or plugin on the server side to remove this restriction, you can ignore this warning.
|
||||
account.methods.offline.uuid=UUID
|
||||
account.methods.offline.uuid.hint=UUID is a unique identifier for Minecraft players, and each launcher may generate UUIDs differently. Editing it to the one generated by other launchers allows you to keep your items in your offline account inventory.\n\
|
||||
account.methods.offline.uuid.hint=UUID is a unique identifier for Minecraft players, and each launcher may generate UUIDs differently. Changing it to the one generated by other launchers allows you to keep your items in your offline account inventory.\n\
|
||||
\n\
|
||||
This option is for advanced users only. We do not recommend editing this option unless you know what you are doing.
|
||||
This option is for advanced users only. We do not recommend changing this option unless you know what you are doing.
|
||||
account.methods.offline.uuid.malformed=Invalid format.
|
||||
account.methods.forgot_password=Forgot Password
|
||||
account.missing=No Accounts
|
||||
@@ -141,18 +141,20 @@ account.move_to_global=Convert to Global Account\nThe account information will b
|
||||
account.move_to_portable=Convert to Portable Account\nThe account information will be saved in a config file in the same directory as HMCL.
|
||||
account.not_logged_in=Not Logged in
|
||||
account.password=Password
|
||||
account.portable=Portable Account
|
||||
account.portable=Portable
|
||||
account.skin=Skin
|
||||
account.skin.file=Skin File
|
||||
account.skin.model=Model
|
||||
account.skin.model.default=Classic
|
||||
account.skin.model.slim=Slim
|
||||
account.skin.type.alex=Alex
|
||||
account.skin.type.csl_api=Blessing Skin
|
||||
account.skin.type.csl_api.location=Address
|
||||
account.skin.type.csl_api.location.hint=CustomSkinAPI URL
|
||||
account.skin.type.little_skin=LittleSkin
|
||||
account.skin.type.little_skin.hint=You need to create a player with the same player name as your offline account on your skin provider site. Your skin will now be set to the skin assigned to your player on the skin provider site.
|
||||
account.skin.type.local_file=Local Skin File
|
||||
account.skin.type.steve=Steve
|
||||
account.skin.upload=Upload/Edit Skin
|
||||
account.skin.upload.failed=Failed to upload skin.
|
||||
account.skin.invalid_skin=Invalid skin file.
|
||||
@@ -165,7 +167,7 @@ archive.version=Version
|
||||
|
||||
assets.download=Downloading Assets
|
||||
assets.download_all=Validating Assets Integrity
|
||||
assets.index.malformed=Index files of downloaded assets were corrupted. You can resolve this problem by clicking "Manage → Update Game Assets" on the "Edit Instance" page.
|
||||
assets.index.malformed=Index files of downloaded assets are corrupted. You can resolve this problem by clicking "Manage → Update Game Assets" on the "Edit Instance" page.
|
||||
|
||||
button.cancel=Cancel
|
||||
button.change_source=Change Download Source
|
||||
@@ -193,7 +195,7 @@ color.recent=Recommended
|
||||
color.custom=Custom Color
|
||||
|
||||
crash.NoClassDefFound=Please verify the integrity of this software, or try updating your Java.
|
||||
crash.user_fault=The launcher crashed due to a corrupted Java or system environment. Please make sure your Java or OS is installed properly.
|
||||
crash.user_fault=The launcher crashed due to corrupted Java or system environment. Please make sure your Java or OS is installed properly.
|
||||
|
||||
curse.category.0=All
|
||||
|
||||
@@ -319,16 +321,18 @@ curse.sort.name=Name
|
||||
curse.sort.popularity=Popularity
|
||||
curse.sort.total_downloads=Total Downloads
|
||||
|
||||
datetime.format=MMM d, yyyy, h\:mm\:ss a
|
||||
|
||||
download=Download
|
||||
download.hint=Install games and modpacks or download mods, resource packs, and worlds.
|
||||
download.code.404=File not found on the remote server: %s
|
||||
download.code.404=File "%s" not found on the remote server.
|
||||
download.content=Addons
|
||||
download.curseforge.unavailable=HMCL Nightly channel build does not support access to CurseForge. Please use Release or Beta channel builds to download.
|
||||
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
|
||||
download.failed=Failed to download "%1$s", response code: %2$d.
|
||||
download.failed.empty=No versions are available. Please click here to go back.
|
||||
download.failed.no_code=Failed to download "%s".
|
||||
download.failed.no_code=Failed to download
|
||||
download.failed.refresh=Failed to fetch version list. Please click here to retry.
|
||||
download.game=New Game
|
||||
download.provider.bmclapi=BMCLAPI (bangbang93, https://bmclapi2.bangbang93.com/)
|
||||
@@ -348,10 +352,10 @@ download.javafx.prepare=Preparing to download
|
||||
|
||||
exception.access_denied=HMCL is unable to access the file "%s". It may be locked by another process.\n\
|
||||
\n\
|
||||
For Windows users, you can open "Resource Monitor" to check if another process is currently using it. If so, you can try again after closing that process.\n\
|
||||
If not, please check if your account has enough permissions to access it.
|
||||
For Windows users, you can open "Resource Monitor" to check if another process is currently using it. If so, you can try again after terminating that process.\n\
|
||||
If not, please check if your user account has adequate permissions to access it.
|
||||
exception.artifact_malformed=Cannot verify the integrity of the downloaded files.
|
||||
exception.ssl_handshake=Failed to establish an SSL connection due to the lack of an SSL certificate in the current Java installation. You can try opening HMCL in another Java and try again.
|
||||
exception.ssl_handshake=Failed to establish SSL connection because the SSL certificate is missing from the current Java installation. You can try opening HMCL with another Java installation and try again.
|
||||
|
||||
extension.bat=Windows Batch File (.bat)
|
||||
extension.mod=Mod File
|
||||
@@ -365,7 +369,7 @@ fatal.javafx.incompatible=Missing JavaFX environment.\n\
|
||||
fatal.javafx.incomplete=The JavaFX environment is incomplete.\n\
|
||||
Please try replacing your Java or reinstalling OpenJFX.
|
||||
fatal.javafx.missing=Missing JavaFX environment. Please open Hello Minecraft! Launcher with Java, which includes OpenJFX.
|
||||
fatal.config_change_owner_root=You are using the root account to open Hello Minecraft! Launcher. This may cause you to fail to open HMCL with another account in the future.\n\
|
||||
fatal.config_change_owner_root=You are using the root account to open Hello Minecraft! Launcher. This may prevent you from opening HMCL with another account in the future.\n\
|
||||
Do you still want to continue?
|
||||
fatal.config_in_temp_dir=You are opening Hello Minecraft! Launcher in a temporary directory. Your settings and game data may be lost.\n\
|
||||
It is recommended to move HMCL to another location and reopen it.\n\
|
||||
@@ -375,7 +379,7 @@ fatal.config_loading_failure=Cannot load configuration files.\n\
|
||||
For macOS, try putting HMCL somewhere with permissions other than "Desktop", "Downloads", and "Documents" and try again.
|
||||
fatal.config_loading_failure.unix=Hello Minecraft! Launcher could not load the configuration file because it was created by user "%1$s".\n\
|
||||
Please open HMCL as root user (not recommended), or execute the following command in the terminal to change the ownership of the configuration file to the current user:\n%2$s
|
||||
fatal.mac_app_translocation=Due to macOS security mechanisms, Hello Minecraft! Launcher is isolated to a temporary directory by the OS.\n\
|
||||
fatal.mac_app_translocation=Hello Minecraft! Launcher is isolated to a temporary directory by the OS due to macOS security mechanisms.\n\
|
||||
Please move HMCL to a different directory before attempting to open. Otherwise, your settings and game data may be lost after restarting.\n\
|
||||
Do you still want to continue?
|
||||
fatal.migration_requires_manual_reboot=Hello Minecraft! Launcher has been upgraded. Please restart the launcher.
|
||||
@@ -388,13 +392,13 @@ fatal.apply_update_need_win7=Hello Minecraft! Launcher cannot automatically upda
|
||||
You can update manually by downloading a newer launcher version from %s.
|
||||
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.
|
||||
fatal.unsupported_platform=Minecraft is not yet fully supported for your platform, so you may experience missing functionality or even be unable to launch the game.\n\
|
||||
fatal.unsupported_platform=Minecraft is not fully supported on your platform yet, so you may experience missing features or even be unable to launch the game.\n\
|
||||
\n\
|
||||
If you cannot launch Minecraft 1.17 and later, you can try switching the "Renderer" to "Software" in "Global/Instance-specific Settings → Advanced Settings" to use CPU rendering for better compatibility.
|
||||
fatal.unsupported_platform.loongarch=Hello Minecraft! Launcher has provided support for the Loongson platform.\n\
|
||||
If you encounter problems when playing a game, you can visit https://docs.hmcl.net/groups.html for help.
|
||||
fatal.unsupported_platform.osx_arm64=Hello Minecraft! Launcher has provided support for the Apple silicon platform, using native ARM Java to launch games to get a smoother game experience.\n\
|
||||
If you encounter problems when playing a game, launching the game with Java based on x86-64 architecture may have better compatibility.
|
||||
fatal.unsupported_platform.osx_arm64=Hello Minecraft! Launcher has provided support for the Apple silicon platform, using native ARM Java to launch games to get a smoother gaming experience.\n\
|
||||
If you encounter problems when playing a game, launching the game with Java based on x86-64 architecture may offer better compatibility.
|
||||
fatal.unsupported_platform.windows_arm64=Hello Minecraft! Launcher has provided native support for the Windows on Arm platform. If you encounter problems when playing a game, please try launching the game with Java based on x86 architecture.\n\
|
||||
\n\
|
||||
If you are using the <b>Qualcomm</b> platform, you may need to install the <a href="ms-windows-store://pdp/?productid=9NQPSL29BFFF">OpenGL Compatibility Pack</a> before playing games.\n\
|
||||
@@ -421,39 +425,39 @@ folder.saves=Saves
|
||||
folder.screenshots=Screenshots
|
||||
|
||||
game=Games
|
||||
game.crash.feedback=<b>Please do not share screenshots of this interface 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.
|
||||
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.
|
||||
game.crash.info=Crash Info
|
||||
game.crash.reason=Crash Cause
|
||||
game.crash.reason.analyzing=Analyzing...
|
||||
game.crash.reason.multiple=Several reasons detected:\n\n
|
||||
game.crash.reason.block=The game crashed due to a block in the world.\n\
|
||||
game.crash.reason.block=The game crashed because of a block in the world.\n\
|
||||
\n\
|
||||
You can try removing this block using MCEdit or deleting the mod that added it.\n\
|
||||
\n\
|
||||
Block Type: %1$s\n\
|
||||
Location: %2$s
|
||||
game.crash.reason.bootstrap_failed=The game crashed due to mod "%1$s".\n\
|
||||
game.crash.reason.bootstrap_failed=The game crashed because of the "%1$s" mod.\n\
|
||||
\n\
|
||||
You can try deleting or updating it.
|
||||
game.crash.reason.config=The game crashed due to a mod "%1$s" cannot parse its config file "%2$s".
|
||||
game.crash.reason.debug_crash=The game crashed due to you triggering it manually. So you probably know why.
|
||||
game.crash.reason.duplicated_mod=The game cannot continue to run due to duplicate mods "%1$s".\n\
|
||||
game.crash.reason.config=The game crashed because the mod "%1$s" could not parse its config file "%2$s".
|
||||
game.crash.reason.debug_crash=The game crashed because you triggered it manually. So you probably know why :)
|
||||
game.crash.reason.duplicated_mod=The game cannot continue to run because of duplicate mods "%1$s".\n\
|
||||
\n\
|
||||
%2$s\n\
|
||||
\n\
|
||||
Each mod can only be installed once. Please delete the duplicate mod and try again.
|
||||
game.crash.reason.entity=The game crashed due to an entity in the world.\n\
|
||||
game.crash.reason.entity=The game crashed because of an entity in the world.\n\
|
||||
\n\
|
||||
You can try removing the entity using MCEdit or deleting the mod that added it.\n\
|
||||
\n\
|
||||
Entity Type: %1$s\n\
|
||||
Location: %2$s
|
||||
game.crash.reason.modmixin_failure=The game crashed due to some mod injection failure.\n\
|
||||
game.crash.reason.modmixin_failure=The game crashed because some mods failed to inject.\n\
|
||||
\n\
|
||||
This generally means that the mod has a bug or is incompatible with the current environment.\n\
|
||||
\n\
|
||||
You can check the log to find the error mod.
|
||||
game.crash.reason.mod_repeat_installation=The game crashed due to duplicate mods.\n\
|
||||
game.crash.reason.mod_repeat_installation=The game crashed because of duplicate mods.\n\
|
||||
\n\
|
||||
Each mod can only be installed once. Please delete the duplicate mod and then relaunch the game.
|
||||
game.crash.reason.forge_error=Forge/NeoForge may have provided error information.\n\
|
||||
@@ -462,140 +466,140 @@ game.crash.reason.forge_error=Forge/NeoForge may have provided error information
|
||||
\n\
|
||||
If you do not see the error message, you can view the error report to understand how the error occurred.\n\
|
||||
%1$s
|
||||
game.crash.reason.mod_resolution0=The game crashed due to some mod problems. You can check the log to find the error mod(s).
|
||||
game.crash.reason.mixin_apply_mod_failed=The game crashed due to the mixin could not be applied to the "%1$s" mod.\n\
|
||||
game.crash.reason.mod_resolution0=The game crashed because of some mod problems. You can check the logs to find the faulty mod(s).
|
||||
game.crash.reason.mixin_apply_mod_failed=The game crashed because the mixin could not be applied to the mod "%1$s".\n\
|
||||
\n\
|
||||
You can try deleting or updating the mod to resolve the problem.
|
||||
game.crash.reason.java_version_is_too_high=The game crashed due to the Java version being too high to continue running.\n\
|
||||
game.crash.reason.java_version_is_too_high=The game crashed because the Java version is too new to continue running.\n\
|
||||
\n\
|
||||
Please use an earlier Java version in "Global/Instance-specific Settings → Java" and then launch the game.\n\
|
||||
Please use the previous major Java version in "Global/Instance-specific Settings → Java" and then launch the game.\n\
|
||||
\n\
|
||||
If not, you can download it from <a href="https://www.java.com/download/">java.com (Java 8)</a> or <a href="https://bell-sw.com/pages/downloads/#downloads">BellSoft Liberica Full JRE (Java 17)</a> and other distributions to download and install one (restart the launcher after installation).
|
||||
game.crash.reason.need_jdk11=The game crashed due to an inappropriate Java VM version.\n\
|
||||
game.crash.reason.need_jdk11=The game crashed because of an inappropriate Java VM version.\n\
|
||||
\n\
|
||||
You need to download and install Java 11, and set it in "Global/Instance-specific Settings → Java".
|
||||
game.crash.reason.mod_name=The game crashed due to mod file name problems.\n\
|
||||
game.crash.reason.mod_name=The game crashed because of mod filename problems.\n\
|
||||
\n\
|
||||
Mod file names should use only English letters (Aa~Zz), numbers (0~9), hyphens (-), underscores (_), and dots (.) in half-width.\n\
|
||||
Mod file names should use only English letters (A~Z, a~z), numbers (0~9), hyphens (-), underscores (_), and dots (.) in half-width.\n\
|
||||
\n\
|
||||
Please navigate to the mod directory and add all non-compliant mod file names with one of the above compliant characters.
|
||||
game.crash.reason.incomplete_forge_installation=The game cannot continue to run due to an incomplete installation of Forge/NeoForge.\n\
|
||||
Please navigate to the mod directory and change all non-compliant mod file names using the compliant characters above.
|
||||
game.crash.reason.incomplete_forge_installation=The game cannot continue to run because of an incomplete Forge/NeoForge installation.\n\
|
||||
\n\
|
||||
Please reinstall Forge/NeoForge in "Edit Instance → Loaders".
|
||||
game.crash.reason.fabric_version_0_12=Fabric Loader 0.12 or later is incompatible with currently installed mods. You need to downgrade it to 0.11.7.
|
||||
game.crash.reason.fabric_warnings=The Fabric warned:\n\
|
||||
game.crash.reason.fabric_warnings=The Fabric Loader warned:\n\
|
||||
\n\
|
||||
%1$s
|
||||
game.crash.reason.file_already_exists=The game crashed due to file "%1$s" already exists.\n\
|
||||
game.crash.reason.file_already_exists=The game crashed because the file "%1$s" already exists.\n\
|
||||
\n\
|
||||
You can try backing up and deleting that file, then relaunching the game.
|
||||
game.crash.reason.file_changed=The game crashed due to file "%1$s" already exists.\n\
|
||||
game.crash.reason.file_changed=The game crashed because the file "%1$s" already exists.\n\
|
||||
\n\
|
||||
You can try backing up and deleting that file, then relaunching the game.
|
||||
game.crash.reason.gl_operation_failure=The game crashed due to some mods, shaders, or resource packs.\n\
|
||||
game.crash.reason.gl_operation_failure=The game crashed because of some mods, shaders, or resource packs.\n\
|
||||
\n\
|
||||
Please disable the mods, shaders, or resource packs you are using and then try again.
|
||||
game.crash.reason.graphics_driver=The game crashed due to a problem with your graphics driver.\n\
|
||||
game.crash.reason.graphics_driver=The game crashed because of a problem with your graphics driver.\n\
|
||||
\n\
|
||||
Please try again after updating your graphics driver to the latest version.\n\
|
||||
\n\
|
||||
If your PC has a discrete graphics card, you need to check whether the game uses integrated/core graphics. If so, please open the launcher using your discrete graphics card. If the problem persists, you probably should consider using a new graphics card or a new PC.\n\
|
||||
If your computer has a dedicated graphics card, you need to check whether the game uses integrated/core graphics. If so, please open the launcher using your dedicated graphics card. If the problem persists, you probably should consider using a new graphics card or a new computer.\n\
|
||||
\n\
|
||||
If you are using your integrated graphics card, please notice that Minecraft 1.16.5 or earlier requires <a href="https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html">Java 1.8.0_51 or earlier</a> for Intel(R) Core(TM) 3000 processor series or earlier.
|
||||
game.crash.reason.macos_failed_to_find_service_port_for_display=The game cannot continue due to a failure to initialize the OpenGL window on the Apple silicon platform.\n\
|
||||
game.crash.reason.macos_failed_to_find_service_port_for_display=The game cannot continue because the OpenGL window on the Apple silicon platform failed to initialize.\n\
|
||||
\n\
|
||||
For this problem, HMCL does not have direct solutions at the moment. Please try opening any browser and going fullscreen, then return to HMCL, launch the game, and <b>quickly return to the browser page</b> before the game window pops up, wait for the game window to appear, and then switch back to the game window.
|
||||
game.crash.reason.illegal_access_error=The game crashed due to some mod(s).\n\
|
||||
game.crash.reason.illegal_access_error=The game crashed because of some mod(s).\n\
|
||||
\n\
|
||||
If you know this: "%1$s", you can update or delete the mod(s) and then try again.
|
||||
game.crash.reason.install_mixinbootstrap=The game crashed due to missing MixinBootstrap.\n\
|
||||
game.crash.reason.install_mixinbootstrap=The game crashed because of the missing MixinBootstrap.\n\
|
||||
\n\
|
||||
You can try installing <a href="https://www.curseforge.com/minecraft/mc-mods/mixinbootstrap">MixinBootstrap</a> to resolve the problem. If it crashes after installation, try adding an exclamation mark (!) in front of the file name of this mod to try to resolve the problem.
|
||||
game.crash.reason.optifine_is_not_compatible_with_forge=The game crashes due to OptiFine being incompatible with the currently installed Forge.\n\
|
||||
game.crash.reason.optifine_is_not_compatible_with_forge=The game crashed because OptiFine is not compatible with the current Forge installation.\n\
|
||||
\n\
|
||||
Please navigate to<a href="https://optifine.net/downloads">the official website of OptiFine</a>, check whether the Forge version is compatible with OptiFine, and reinstall the instance in strict accordance with the corresponding version, or change the OptiFine version in "Edit Instance → Loaders".\n\
|
||||
\n\
|
||||
After testing, we believe that too high or too low OptiFine versions may cause crashes.
|
||||
game.crash.reason.mod_files_are_decompressed=The game crashed due to the mod file having been extracted.\n\
|
||||
game.crash.reason.mod_files_are_decompressed=The game crashed because the mod file was extracted.\n\
|
||||
\n\
|
||||
Please put the entire mod file directly into the mod directory!\n\
|
||||
\n\
|
||||
If extract will cause errors in the game, please delete the extracted mod in the mod directory and then launch the game.
|
||||
game.crash.reason.shaders_mod=The game crashed due to both OptiFine and Shaders mods being installed.\n\
|
||||
If extraction causes errors in the game, please delete the extracted mod in the mod directory and then launch the game.
|
||||
game.crash.reason.shaders_mod=The game crashed because of both OptiFine and Shaders mod are installed at the same time.\n\
|
||||
\n\
|
||||
Just remove Shaders mod due to OptiFine having built-in support for shaders.
|
||||
game.crash.reason.rtss_forest_sodium=The game crashed due to RivaTuner Statistics Server (RTSS) being incompatible with Sodium.\n\
|
||||
Just remove the Shader mod because OptiFine has built-in support for shaders.
|
||||
game.crash.reason.rtss_forest_sodium=The game crashed because the RivaTuner Statistical Server (RTSS) is not compatible with Sodium.\n\
|
||||
\n\
|
||||
Click <a href="https://github.com/CaffeineMC/sodium-fabric/wiki/Known-Issues#rtss-incompatible">here</a> for more details.
|
||||
game.crash.reason.too_many_mods_lead_to_exceeding_the_id_limit=The game crashed due to you having installed too many mods, which exceeds the ID limit of the game.\n\
|
||||
game.crash.reason.too_many_mods_lead_to_exceeding_the_id_limit=The game crashed because you installed too many mods and exceeded the game ID limit.\n\
|
||||
\n\
|
||||
Please try installing<a href="https://www.curseforge.com/minecraft/mc-mods/jeid">JEID</a> or deleting some large mods.
|
||||
game.crash.reason.night_config_fixes=The game crashed due to some problems with Night Config.\n\
|
||||
game.crash.reason.night_config_fixes=The game crashed because of some problems with Night Config.\n\
|
||||
\n\
|
||||
You can try installing the <a href="https://www.curseforge.com/minecraft/mc-mods/night-config-fixes">Night Config Fixes</a> mod, which may help you with this problem.\n\
|
||||
\n\
|
||||
For more information, visit the <a href="https://github.com/Fuzss/nightconfigfixes">GitHub repository</a> of this mod.
|
||||
game.crash.reason.optifine_causes_the_world_to_fail_to_load=The game may not continue to run due to OptiFine.\n\
|
||||
game.crash.reason.optifine_causes_the_world_to_fail_to_load=The game may not continue to run because of the OptiFine.\n\
|
||||
\n\
|
||||
This problem only occurs in a specific OptiFine version. You can try changing the OptiFine version in "Edit Instance → Loaders".
|
||||
game.crash.reason.jdk_9=The game crashed due to the Java version being too new for this instance.\n\
|
||||
game.crash.reason.jdk_9=The game crashed because the Java version is too new for this instance.\n\
|
||||
\n\
|
||||
You need to download and install Java 8 and choose it in "Global/Instance-specific Settings → Java".
|
||||
game.crash.reason.jvm_32bit=The game crashed due to the current memory allocation exceeding the limit of the 32-bit Java VM.\n\
|
||||
game.crash.reason.jvm_32bit=The game crashed because the current memory allocation exceeded the 32-bit Java VM limit.\n\
|
||||
\n\
|
||||
If your OS is 64-bit, please install and use a 64-bit Java version. Otherwise, you may need to reinstall a 64-bit OS or get a moderner PC.\n\
|
||||
If your OS is 64-bit, please install and use a 64-bit Java version. Otherwise, you may need to reinstall a 64-bit OS or get a moderner computer.\n\
|
||||
\n\
|
||||
Or, you can disable the "Automatically Allocate" option in "Global/Instance-specific Settings → Memory" and set the maximum memory allocation size to 1024 MB or below.
|
||||
game.crash.reason.loading_crashed_forge=The game crashed due to mod "%1$s" (%2$s).\n\
|
||||
game.crash.reason.loading_crashed_forge=The game crashed because of the mod "%1$s" (%2$s).\n\
|
||||
\n\
|
||||
You can try deleting or updating it.
|
||||
game.crash.reason.loading_crashed_fabric=The game crashed due to mod "%1$s".\n\
|
||||
game.crash.reason.loading_crashed_fabric=The game crashed because of the mod "%1$s".\n\
|
||||
\n\
|
||||
You can try deleting or updating it.
|
||||
game.crash.reason.mac_jdk_8u261=The game crashed due to your current Forge or OptiFine version not being compatible with your Java installation.\n\
|
||||
game.crash.reason.mac_jdk_8u261=The game crashed because your current Forge or OptiFine version is incompatible with your Java installation.\n\
|
||||
\n\
|
||||
Please try updating Forge and OptiFine, or try using Java 8u251 or earlier versions.
|
||||
game.crash.reason.forge_repeat_installation=The game crashed due to a duplicate installation of Forge. <a href="https://github.com/HMCL-dev/HMCL/issues/1880">This is a known problem</a>\n\
|
||||
Please try updating Forge and OptiFine, or try using Java 8u251 or previous versions.
|
||||
game.crash.reason.forge_repeat_installation=The game crashed because of a duplicate Forge installation. <a href="https://github.com/HMCL-dev/HMCL/issues/1880">This is a known problem</a>\n\
|
||||
\n\
|
||||
It is recommended to upload the log feedback to GitHub so that we can find more clues and resolve this problem.\n\
|
||||
\n\
|
||||
Currently you can uninstall Forge and reinstall it in "Edit Instance → Loaders".
|
||||
game.crash.reason.optifine_repeat_installation=The game crashed due to repeated installation of OptiFine.\n\
|
||||
game.crash.reason.optifine_repeat_installation=The game crashed because of a duplicate OptiFine installation.\n\
|
||||
\n\
|
||||
Please delete OptiFine in the mod directory or uninstall it in "Edit Instance → Loaders".
|
||||
game.crash.reason.memory_exceeded=The game crashed due to too much memory allocated for a small page file.\n\
|
||||
game.crash.reason.memory_exceeded=The game crashed because too much memory was allocated for a small page file.\n\
|
||||
\n\
|
||||
You can try disabling the "Automatically Allocate" option in "Global/Instance-specific Settings → Memory" and adjust the value till the game launches.\n\
|
||||
\n\
|
||||
You can also try increasing the page file size in system settings.
|
||||
game.crash.reason.mod=The game crashed due to the mod "%1$s".\n\
|
||||
game.crash.reason.mod=The game crashed because of the mod "%1$s".\n\
|
||||
\n\
|
||||
You may update or delete the mod and then try again.
|
||||
game.crash.reason.mod_resolution=The game cannot continue to run due to mod dependency problems.\n\
|
||||
game.crash.reason.mod_resolution=The game cannot continue to run because of mod dependency problems.\n\
|
||||
\n\
|
||||
Fabric provided the following details:\n\
|
||||
\n\
|
||||
%1$s
|
||||
game.crash.reason.forgemod_resolution=The game cannot continue to run due to mod dependency problems.\n\
|
||||
game.crash.reason.forgemod_resolution=The game cannot continue to run because of mod dependency problems.\n\
|
||||
\n\
|
||||
Forge/NeoForge provided the following details:\n\
|
||||
%1$s
|
||||
game.crash.reason.forge_found_duplicate_mods=The game crashed due to a duplicate mods problem. Forge/NeoForge provides the following information:\n\
|
||||
game.crash.reason.forge_found_duplicate_mods=The game crashed because of a duplicate mod problem. Forge/NeoForge provided the following information:\n\
|
||||
%1$s
|
||||
game.crash.reason.mod_resolution_collection=The game crashed because the mod version is not compatible.\n\
|
||||
\n\
|
||||
"%1$s" requires "%2$s".\n\
|
||||
\n\
|
||||
You need to upgrade or downgrade %3$s before continuing.
|
||||
game.crash.reason.mod_resolution_conflict=The game crashed due to conflicting mods.\n\
|
||||
game.crash.reason.mod_resolution_conflict=The game crashed because of conflicting mods.\n\
|
||||
\n\
|
||||
"%1$s" is incompatible with "%2$s".
|
||||
game.crash.reason.mod_resolution_missing=The game crashed due to some dependency mods not being installed.\n\
|
||||
game.crash.reason.mod_resolution_missing=The game crashed because some dependent mods were not installed.\n\
|
||||
\n\
|
||||
"%1$s" requires mod "%2$s".\n\
|
||||
\n\
|
||||
This means that you have to download and install "%2$s" first to continue playing.
|
||||
game.crash.reason.mod_resolution_missing_minecraft=The game crashed due to a mod being incompatible with the current Minecraft version.\n\
|
||||
game.crash.reason.mod_resolution_missing_minecraft=The game crashed because a mod is incompatible with the current Minecraft version.\n\
|
||||
\n\
|
||||
"%1$s" requires Minecraft version %2$s.\n\
|
||||
\n\
|
||||
@@ -604,31 +608,31 @@ game.crash.reason.mod_resolution_missing_minecraft=The game crashed due to a mod
|
||||
Otherwise, you should install a version that is compatible with this Minecraft version.
|
||||
game.crash.reason.mod_resolution_mod_version=%1$s (Version: %2$s)
|
||||
game.crash.reason.mod_resolution_mod_version.any=%1$s (Any Version)
|
||||
game.crash.reason.modlauncher_8=The game crashed due to your current Forge version not being compatible with your Java installation. Please try updating Forge.
|
||||
game.crash.reason.no_class_def_found_error=The game cannot continue to run due to incomplete code.\n\
|
||||
game.crash.reason.modlauncher_8=The game crashed because your current Forge version is not compatible with your Java installation. Please try updating Forge.
|
||||
game.crash.reason.no_class_def_found_error=The game cannot continue to run because of incomplete code.\n\
|
||||
\n\
|
||||
Your game instance is missing %1$s. This might be due to a mod missing, an incompatible mod installed, or some files being corrupted.\n\
|
||||
Your game instance is missing %1$s. This might be because a mod is missing, an incompatible mod is installed, or some files are corrupted.\n\
|
||||
\n\
|
||||
You may need to reinstall the game and all mods or ask someone for help.
|
||||
game.crash.reason.no_such_method_error=The game cannot continue to run due to incomplete code.\n\
|
||||
game.crash.reason.no_such_method_error=The game cannot continue to run because of incomplete code.\n\
|
||||
\n\
|
||||
Your game instance might be missing a mod, installed an incompatible mod, or some files might be corrupted.\n\
|
||||
\n\
|
||||
You may need to reinstall the game and all mods or ask someone for help.
|
||||
game.crash.reason.opengl_not_supported=The game crashed due to OpenGL not being supported by your graphics driver.\n\
|
||||
game.crash.reason.opengl_not_supported=The game crashed because your graphics driver does not support OpenGL.\n\
|
||||
\n\
|
||||
If you are streaming the game over the Internet or using a remote desktop environment, please play the game on your local one.\n\
|
||||
If you are streaming the game over the Internet or using a remote desktop environment, please play the game on your local machine.\n\
|
||||
\n\
|
||||
Or, you can try updating your driver to the latest version and then try again.\n\
|
||||
Or, you can update your graphic driver to the latest version and then try again.\n\
|
||||
\n\
|
||||
If your PC has a discrete graphics card, please make sure the game is actually using it for rendering. If the problem persists, please consider getting a new graphics card or a new PC.
|
||||
game.crash.reason.openj9=The game is unable to run on an OpenJ9 VM. Please switch to a Java that uses the Hotspot VM in "Global/Instance-specific Settings → Java" and relaunch the game. If you do not have one, you can download one online.
|
||||
game.crash.reason.out_of_memory=The game crashed due to it running out of memory.\n\
|
||||
If your computer has a dedicated graphics card, please make sure the game is indeed using it for rendering. If the problem persists, please consider getting a new graphics card or a new computer.
|
||||
game.crash.reason.openj9=The game is unable to run on an OpenJ9 VM. Please switch to a Java that uses the Hotspot VM in "Global/Instance-specific Settings → Java" and relaunch the game. If you do not have one, you can download one.
|
||||
game.crash.reason.out_of_memory=The game crashed because the computer ran out of memory.\n\
|
||||
\n\
|
||||
Maybe because there is not enough memory available or too many mods installed. You can try resolving it by increasing the allocated memory in "Global/Instance-specific Settings → Memory".\n\
|
||||
Maybe there is not enough memory available or too many mods installed. You can try resolving it by increasing the allocated memory in "Global/Instance-specific Settings → Memory".\n\
|
||||
\n\
|
||||
If you still encounter these problems, you may need a better PC.
|
||||
game.crash.reason.resolution_too_high=The game crashed due to the resource pack resolution being too high.\n\
|
||||
If you still encounter these problems, you may need a better computer.
|
||||
game.crash.reason.resolution_too_high=The game crashed because the resource pack resolution is too high.\n\
|
||||
\n\
|
||||
You should switch to a resource pack with lower resolution or consider buying a better graphics card with more VRAM.
|
||||
game.crash.reason.stacktrace=The crash reason is unknown. You can view its details by clicking "Logs".\n\
|
||||
@@ -636,15 +640,15 @@ game.crash.reason.stacktrace=The crash reason is unknown. You can view its detai
|
||||
There are some keywords that might contain some mod names. You can search them online to figure out the problem yourself.\n\
|
||||
\n\
|
||||
%s
|
||||
game.crash.reason.too_old_java=The game crashed due to you using an outdated Java VM version.\n\
|
||||
game.crash.reason.too_old_java=The game crashed because you are using an outdated Java VM version.\n\
|
||||
\n\
|
||||
You need to switch to a newer version (%1$s) of Java in "Global/Instance-specific Settings → Java" and then relaunch the game. You can download Java from <a href="https://learn.microsoft.com/java/openjdk/download">here</a>.
|
||||
You need to switch to a newer Java version (%1$s) in "Global/Instance-specific Settings → Java" and then relaunch the game. You can download Java from <a href="https://learn.microsoft.com/java/openjdk/download">here</a>.
|
||||
game.crash.reason.unknown=We are not able to figure out why the game crashed. Please refer to the game logs.
|
||||
game.crash.reason.unsatisfied_link_error=Failed to launch Minecraft due to missing libraries: %1$s.\n\
|
||||
game.crash.reason.unsatisfied_link_error=Failed to launch Minecraft because of missing libraries: %1$s.\n\
|
||||
\n\
|
||||
If you have edited the native library path, please make sure these libraries do exist. Or, please try launching again after reverting it to default.\n\
|
||||
\n\
|
||||
If you did not, please check if you have missing dependency mods.\n\
|
||||
If you have not, please check if you have missing dependency mods.\n\
|
||||
\n\
|
||||
Otherwise, if you believe this is caused by HMCL, please provide feedback to us.
|
||||
game.crash.title=Game Crashed
|
||||
@@ -664,7 +668,7 @@ install=New Instance
|
||||
install.change_version=Change Version
|
||||
install.change_version.confirm=Are you sure you want to switch %s from version %s to %s?
|
||||
install.failed=Failed to install
|
||||
install.failed.downloading=We are unable to download some required files.
|
||||
install.failed.downloading=Failed to download some required files.
|
||||
install.failed.downloading.detail=Failed to download file: %s
|
||||
install.failed.downloading.timeout=Download timeout when fetching: %s
|
||||
install.failed.install_online=Failed to identify the provided file. If you are installing a mod, navigate to the "Mods" page.
|
||||
@@ -677,7 +681,7 @@ install.installer.choose=Choose Your %s Version
|
||||
install.installer.depend=Requires %s
|
||||
install.installer.fabric=Fabric
|
||||
install.installer.fabric-api=Fabric API
|
||||
install.installer.fabric-api.warning=Warning: Fabric API is a mod and will be installed into the mod directory of the game instance. Please do not change the working directory of the game, or the Fabric API will not work. If you do want to change these settings, you should reinstall it.
|
||||
install.installer.fabric-api.warning=Warning: Fabric API is a mod and will be installed into the mod directory of the game instance. Please do not change the working directory of the game, or the Fabric API won't function. If you do want to change the directory, you should reinstall it.
|
||||
install.installer.forge=Forge
|
||||
install.installer.neoforge=NeoForge
|
||||
install.installer.game=Minecraft
|
||||
@@ -696,12 +700,13 @@ install.installer.quilt-api=QSL/QFAPI
|
||||
install.installer.version=%s
|
||||
install.installer.external_version=%s (Installed by external process, which cannot be configured)
|
||||
install.modpack=Install Modpack
|
||||
install.name.invalid=The name contains non-ASCII characters (such as emoji or CJK characters).\nIt is recommended to change the name to include only English letters, numbers, and underscores to avoid potential issues when launching the game.\nDo you want to proceed with the installation?
|
||||
install.new_game=Install Instance
|
||||
install.new_game.already_exists=This instance name already exists. Please use another name.
|
||||
install.new_game.current_game_version=Current Instance Version
|
||||
install.new_game.malformed=Invalid name.
|
||||
install.select=Choose operation
|
||||
install.success=Installed successfully.
|
||||
install.success=Successfully installed.
|
||||
|
||||
java.add=Add Java
|
||||
java.add.failed=This Java is invalid or incompatible with the current platform.
|
||||
@@ -711,6 +716,7 @@ java.disabled.management=Disabled Java
|
||||
java.disabled.management.remove=Remove this Java from the list
|
||||
java.disabled.management.restore=Re-enable this Java
|
||||
java.download=Download Java
|
||||
java.download.banshanjdk-8=Download Banshan JDK 8
|
||||
java.download.load_list.failed=Failed to load version list
|
||||
java.download.more=More Java distributions
|
||||
java.download.prompt=Please choose the Java version you want to download:
|
||||
@@ -735,32 +741,33 @@ java.uninstall.confirm=Are you sure you want to uninstall this Java? This action
|
||||
|
||||
lang=English
|
||||
lang.default=Use System Locales
|
||||
|
||||
launch.advice=%s Do you still want to continue to launch?
|
||||
launch.advice.multi=The following problems were detected:\n\n%s\n\nThese problems may cause you to be unable to launch the game or affect the game experience. \nDo you still want to continue to launch?
|
||||
launch.advice.multi=The following problems were detected:\n\n%s\n\nThese problems may prevent you from launching the game or affect gaming experience.\nDo you still want to continue to launch?
|
||||
launch.advice.java.auto=The current Java VM version is not compatible with the instance.\n\nClick "Yes" to automatically choose the most compatible Java VM version. Or, you can navigate to "Global/Instance-specific Settings → Java" to choose one yourself.
|
||||
launch.advice.java.modded_java_7=Minecraft 1.7.2 and earlier versions require Java 7 or earlier.
|
||||
launch.advice.corrected=We have resolved the Java VM problem. If you still want to use your choice of Java version, you can disable the "Do not check Java VM compatibility" in "Global/Instance-specific Settings → Advanced Settings".
|
||||
launch.advice.uncorrected=If you still want to use your choice of Java version, you can disable the "Do not check Java VM compatibility" in "Global/Instance-specific Settings → Advanced Settings".
|
||||
launch.advice.java.modded_java_7=Minecraft 1.7.2 and previous versions require Java 7 or earlier.
|
||||
launch.advice.corrected=We have resolved the Java VM problem. If you still want to use your choice of Java version, you can disable "Do not check Java VM compatibility" in "Global/Instance-specific Settings → Advanced Settings".
|
||||
launch.advice.uncorrected=If you still want to use your choice of Java version, you can disable "Do not check Java VM compatibility" in "Global/Instance-specific Settings → Advanced Settings".
|
||||
launch.advice.different_platform=The 64-bit Java version is recommended for your device, but you have installed a 32-bit one.
|
||||
launch.advice.forge2760_liteloader=Forge version 2760 is not compatible with LiteLoader. Please consider upgrading Forge to version 2773 or later.
|
||||
launch.advice.forge28_2_2_optifine=Forge version 28.2.2 or later is not compatible with OptiFine. Please consider downgrading Forge to version 28.2.1 or earlier.
|
||||
launch.advice.forge37_0_60=Forge versions earlier than 37.0.60 are not compatible with Java 17. Please update Forge to 37.0.60 or later, or launch the game with Java 16.
|
||||
launch.advice.forge37_0_60=Forge versions prior to 37.0.60 are not compatible with Java 17. Please update Forge to 37.0.60 or later, or launch the game with Java 16.
|
||||
launch.advice.java8_1_13=Minecraft 1.13 and later can only be run on Java 8 or later. Please use Java 8 or later versions.
|
||||
launch.advice.java8_51_1_13=Minecraft 1.13 may crash on Java 8 versions earlier than 1.8.0_51. Please install the latest Java 8 version.
|
||||
launch.advice.java8_51_1_13=Minecraft 1.13 may crash on Java 8 versions prior to 1.8.0_51. Please install the latest Java 8 version.
|
||||
launch.advice.java9=You cannot launch Minecraft 1.12 or earlier with Java 9 or later. Please use Java 8 instead.
|
||||
launch.advice.modded_java=Some mods may not be compatible with newer versions of Java. It is recommended to use Java %s to launch Minecraft %s.
|
||||
launch.advice.modded_java=Some mods may not be compatible with newer Java versions. It is recommended to use Java %s to launch Minecraft %s.
|
||||
launch.advice.modlauncher8=The Forge version you are using is not compatible with the current Java version. Please try updating Forge.
|
||||
launch.advice.newer_java=You are using the old Java to launch the game. It is recommended to update to Java 8, otherwise some mods may cause the game to crash.
|
||||
launch.advice.not_enough_space=You have allocated a memory size larger than the actual %d MB of memory installed on your PC. You may experience degraded performance or even be unable to launch the game.
|
||||
launch.advice.newer_java=You are using an older Java version to launch the game. It is recommended to update to Java 8, otherwise some mods may cause the game to crash.
|
||||
launch.advice.not_enough_space=You have allocated a memory size larger than the actual %d MB of memory installed on your computer. You may experience degraded performance or even be unable to launch the game.
|
||||
launch.advice.require_newer_java_version=The current game version requires Java %s, but we could not find one. Do you want to download one now?
|
||||
launch.advice.too_large_memory_for_32bit=You have allocated a memory size larger than the memory limitation of the 32-bit Java installation. You may be unable to launch the game.
|
||||
launch.advice.vanilla_linux_java_8=Minecraft 1.12.2 or earlier only supports Java 8 for the Linux x86-64 platform because the later versions cannot load 32-bit native libraries like liblwjgl.so\n\nPlease download it from java.com or install OpenJDK 8.
|
||||
launch.advice.vanilla_x86.translation=Minecraft is not fully supported for your platform, so you may experience missing functionality or even be unable to launch the game.\nYou can download Java for the <b>x86-64</b> architecture <a href="https://learn.microsoft.com/java/openjdk/download">here</a> for a full gaming experience.
|
||||
launch.advice.vanilla_x86.translation=Minecraft is not fully supported on your platform, so you may experience missing features or even be unable to launch the game.\nYou can download Java for the <b>x86-64</b> architecture <a href="https://learn.microsoft.com/java/openjdk/download">here</a> for a full gaming experience.
|
||||
launch.advice.unknown=The game cannot be launched due to the following reasons:
|
||||
launch.failed=Failed to launch
|
||||
launch.failed.cannot_create_jvm=We are unable to create a Java VM. It may be caused by incorrect Java VM arguments. You can try resolving it by removing all arguments you added in "Global/Instance-specific Settings → Advanced Settings → Java VM Options".
|
||||
launch.failed.creating_process=We are unable to create a new process. Please check your Java path.\n
|
||||
launch.failed.command_too_long=The command length exceeds the maximum length of a bat script. Please try exporting it as a PowerShell script.
|
||||
launch.failed.command_too_long=The command length exceeds the maximum length of a batch script. Please try exporting it as a PowerShell script.
|
||||
launch.failed.decompressing_natives=Failed to extract native libraries.\n
|
||||
launch.failed.download_library=Failed to download libraries "%s".
|
||||
launch.failed.executable_permission=Failed to make the launch script executable.
|
||||
@@ -787,7 +794,8 @@ launcher.agreement.hint=You must agree to the EULA to use this software.
|
||||
launcher.background=Background Image
|
||||
launcher.background.choose=Choose background image
|
||||
launcher.background.classic=Classic
|
||||
launcher.background.default=Default (Or "background.png/.jpg/.gif" and the images in the "bg" directory)
|
||||
launcher.background.default=Default
|
||||
launcher.background.default.tooltip=Or "background.png/.jpg/.gif/.webp" and the images in the "bg" directory
|
||||
launcher.background.network=From URL
|
||||
launcher.background.translucent=Translucent
|
||||
launcher.cache_directory=Cache Directory
|
||||
@@ -798,7 +806,7 @@ launcher.cache_directory.disabled=Disabled
|
||||
launcher.cache_directory.invalid=Failed to create a cache directory, falling back to default.
|
||||
launcher.contact=Contact Us
|
||||
launcher.crash=Hello Minecraft! Launcher has encountered a fatal error! Please copy the following log and ask for help on our Discord, QQ group, GitHub, or other Minecraft forum.
|
||||
launcher.crash.java_internal_error=Hello Minecraft! Launcher has encountered a fatal error due to your Java being broken. Please uninstall your Java and download a suitable Java <a href="https://bell-sw.com/pages/downloads/#downloads">here</a>.
|
||||
launcher.crash.java_internal_error=Hello Minecraft! Launcher has encountered a fatal error because your Java is corrupted. Please uninstall your Java and download a suitable Java <a href="https://bell-sw.com/pages/downloads/#downloads">here</a>.
|
||||
launcher.crash.hmcl_out_dated=Hello Minecraft! Launcher has encountered a fatal error! Your launcher is outdated. Please update your launcher!
|
||||
launcher.update_java=Please update your Java version.
|
||||
|
||||
@@ -816,7 +824,7 @@ logwindow.export_dump.no_dependency=Your Java does not contain the dependencies
|
||||
|
||||
main_page=Home
|
||||
|
||||
message.cancelled=Operation canceled
|
||||
message.cancelled=Operation Canceled
|
||||
message.confirm=Confirm
|
||||
message.copied=Copied to clipboard
|
||||
message.default=Default
|
||||
@@ -825,7 +833,7 @@ message.downloading=Downloading
|
||||
message.error=Error
|
||||
message.failed=Operation Failed
|
||||
message.info=Information
|
||||
message.success=Operation completed successfully
|
||||
message.success=Operation successfully completed
|
||||
message.unknown=Unknown
|
||||
message.warning=Warning
|
||||
|
||||
@@ -842,6 +850,7 @@ modpack.completion=Downloading dependencies
|
||||
modpack.desc=Describe your modpack, including an introduction and probably some changelog. Markdown and images from URL are currently supported.
|
||||
modpack.description=Modpack Description
|
||||
modpack.download=Download Modpacks
|
||||
modpack.download.title=Download Modpack - %1s
|
||||
modpack.enter_name=Enter a name for this modpack.
|
||||
modpack.export=Export as Modpack
|
||||
modpack.export.as=Export Modpack As...
|
||||
@@ -863,7 +872,7 @@ modpack.install=Install Modpack %s
|
||||
modpack.installing=Installing Modpack
|
||||
modpack.introduction=Curse, Modrinth, MultiMC, and MCBBS modpacks are currently supported.
|
||||
modpack.invalid=Invalid modpack, you can try downloading it again.
|
||||
modpack.mismatched_type=Modpack type mismatched, the current instance is a(an) %s type, but the provided one is %s type.
|
||||
modpack.mismatched_type=Modpack type mismatched, the current instance is a(n) %s type, but the provided one is %s type.
|
||||
modpack.name=Modpack Name
|
||||
modpack.not_a_valid_name=Invalid modpack name.
|
||||
modpack.origin=Source
|
||||
@@ -883,8 +892,8 @@ modpack.type.modrinth=Modrinth
|
||||
modpack.type.multimc=MultiMC
|
||||
modpack.type.multimc.export=Can be imported by Hello Minecraft! Launcher and MultiMC
|
||||
modpack.type.server=Auto-Update Modpack from Server
|
||||
modpack.type.server.export=Allows server owner to update the game instance remotely
|
||||
modpack.type.server.malformed=Invalid modpack manifest. Please refer to the modpack maker to resolve this problem.
|
||||
modpack.type.server.export=Allows server owner to remotely update the game instance
|
||||
modpack.type.server.malformed=Invalid modpack manifest. Please contact the modpack maker to resolve this problem.
|
||||
modpack.unsupported=Unsupported modpack format
|
||||
modpack.update=Updating modpack
|
||||
modpack.wizard=Modpack Export Guide
|
||||
@@ -898,10 +907,10 @@ modpack.wizard.step.initialization.exported_version=Game version to export
|
||||
modpack.wizard.step.initialization.force_update=Force updating the modpack to the latest version (you will need a file-hosting server)
|
||||
modpack.wizard.step.initialization.include_launcher=Include the launcher
|
||||
modpack.wizard.step.initialization.save=Export to...
|
||||
modpack.wizard.step.initialization.warning=Before making a modpack, please make sure the game launches normally and Minecraft is a release version instead of a snapshot. The launcher will save your download settings.\n\
|
||||
modpack.wizard.step.initialization.warning=Before making a modpack, please make sure the game can be launched normally and Minecraft is a release version instead of a snapshot. The launcher will save your download settings.\n\
|
||||
\n\
|
||||
Keep in mind that you are not allowed to add mods and resource packs that are explicitly said not to be distributed or put in a modpack.
|
||||
modpack.wizard.step.initialization.server=Click here for more tutorials for making a server modpack that can be automatically updated.
|
||||
Keep in mind that you are not allowed to add mods and resource packs that explicitly state they could not to be distributed or put in a modpack.
|
||||
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.audio=Audio
|
||||
@@ -975,9 +984,9 @@ modrinth.category.512x+=512x+
|
||||
mods=Mods
|
||||
mods.add=Add Mod
|
||||
mods.add.failed=Failed to add mod %s.
|
||||
mods.add.success=%s was added successfully.
|
||||
mods.add.success=%s was successfully added.
|
||||
mods.broken_dependency.title=Broken dependency
|
||||
mods.broken_dependency.desc=This dependency existed before, but it does not exist now. Try using another download source.
|
||||
mods.broken_dependency.desc=This dependency existed before, but it does not exist anymore. Try using another download source.
|
||||
mods.category=Category
|
||||
mods.channel.alpha=Alpha
|
||||
mods.channel.beta=Beta
|
||||
@@ -985,23 +994,24 @@ mods.channel.release=Release
|
||||
mods.check_updates=Check for Updates
|
||||
mods.check_updates.current_version=Current Version
|
||||
mods.check_updates.empty=All mods are up-to-date
|
||||
mods.check_updates.failed=Failed to download some files.
|
||||
mods.check_updates.failed_check=Failed to check for updates.
|
||||
mods.check_updates.failed_download=Failed to download some files.
|
||||
mods.check_updates.file=File
|
||||
mods.check_updates.source=Source
|
||||
mods.check_updates.target_version=Target Version
|
||||
mods.check_updates.update=Update
|
||||
mods.choose_mod=Choose mod
|
||||
mods.curseforge=CurseForge
|
||||
mods.dependency.embedded=Built-in Pre-mod (Already packaged in the mod file by the author. No need to download separately)
|
||||
mods.dependency.optional=Optional Pre-mod (If missing, the game will run normally, but the mod features may be missing)
|
||||
mods.dependency.required=Required Pre-mod (Must be downloaded separately. Missing may cause the game to fail to launch)
|
||||
mods.dependency.tool=Required Library (Must be downloaded separately. Missing may cause the game to fail to launch)
|
||||
mods.dependency.include=Built-in Pre-mod (Already packaged in the mod file by the author, no need to download separately)
|
||||
mods.dependency.incompatible=Incompatible Mod (Installing these mods at the same time will cause the game to fail to launch)
|
||||
mods.dependency.broken=Broken Pre-mod (This mod existed before, but it does not exist now. Try using another download source.)
|
||||
mods.dependency.embedded=Built-in Dependencies (Already packaged in the mod file by the author. No need to download separately)
|
||||
mods.dependency.optional=Optional Dependencies (If missing, the game will run normally, but the mod features may be missing)
|
||||
mods.dependency.required=Required Dependencies (Must be downloaded separately. Missing may prevent the game from launching)
|
||||
mods.dependency.tool=Required Dependencies (Must be downloaded separately. Missing may prevent the game from launching)
|
||||
mods.dependency.include=Built-in Dependencies (Already packaged in the mod file by the author. No need to download separately)
|
||||
mods.dependency.incompatible=Incompatible Mods (Installing these mods at the same time will prevent the game from launching)
|
||||
mods.dependency.broken=Broken Dependencies (This mod existed before, but it does not exist anymore. Try using another download source.)
|
||||
mods.disable=Disable
|
||||
mods.download=Mod Download
|
||||
mods.download.title=Mod Download - %1s
|
||||
mods.download=Download Mod
|
||||
mods.download.title=Download Mod - %1s
|
||||
mods.download.recommend=Recommended Mod Version - Minecraft %1s
|
||||
mods.enable=Enable
|
||||
mods.manage=Mods
|
||||
@@ -1011,7 +1021,7 @@ mods.mcmod.page=MCMod Page
|
||||
mods.mcmod.search=Search in MCMod
|
||||
mods.modrinth=Modrinth
|
||||
mods.name=Name
|
||||
mods.not_modded=You must install a mod loader (Forge, NeoForge, Fabric, Quilt, or LiteLoader) first to manage your mods!
|
||||
mods.not_modded=You must install a modloader (Forge, NeoForge, Fabric, Quilt, or LiteLoader) first to manage your mods!
|
||||
mods.restore=Restore
|
||||
mods.url=Official Page
|
||||
mods.update_modpack_mod.warning=Updating mods in a modpack can lead to irreparable results, possibly corrupting the modpack so that it cannot launch. Are you sure you want to update?
|
||||
@@ -1083,7 +1093,6 @@ world.name=World Name
|
||||
world.name.enter=Enter the world name
|
||||
world.reveal=Reveal in Explorer
|
||||
world.show_all=Show All
|
||||
world.time=h\:mm\:ss a, EEE, MMM d yyyy
|
||||
|
||||
profile=Game Directories
|
||||
profile.already_exists=This name already exists. Please use a different name.
|
||||
@@ -1167,7 +1176,7 @@ settings.advanced.natives_directory=Native Library Path
|
||||
settings.advanced.natives_directory.choose=Choose the location of the desired native library
|
||||
settings.advanced.natives_directory.custom=Custom
|
||||
settings.advanced.natives_directory.default=Default
|
||||
settings.advanced.natives_directory.hint=This option is intended only for users of Apple M1 or other not officially supported platforms. Please do not edit this option unless you know what you are doing.\n\
|
||||
settings.advanced.natives_directory.hint=This option is intended only for users of Apple silicon or other not officially supported platforms. Please do not edit this option unless you know what you are doing.\n\
|
||||
\n\
|
||||
Before proceeding, please make sure all libraries (e.g. lwjgl.dll, libopenal.so) are provided in your desired directory.\n\
|
||||
Note: It is recommended to use a fully English-letters path for the specified local library file. Otherwise it may lead to game launch failure.
|
||||
@@ -1199,6 +1208,9 @@ settings.advanced.wrapper_launcher.prompt=Allows launching using an extra wrappe
|
||||
settings.custom=Custom
|
||||
|
||||
settings.game=Settings
|
||||
settings.game.copy_global=Copy from Global Settings
|
||||
settings.game.copy_global.copy_all=Copy All
|
||||
settings.game.copy_global.copy_all.confirm=Are you sure you want to overwrite the current instance settings? This action cannot be undone!
|
||||
settings.game.current=Game
|
||||
settings.game.dimension=Resolution
|
||||
settings.game.exploration=Explore
|
||||
@@ -1248,7 +1260,7 @@ settings.launcher.proxy.http=HTTP
|
||||
settings.launcher.proxy.none=No Proxy
|
||||
settings.launcher.proxy.password=Password
|
||||
settings.launcher.proxy.port=Port
|
||||
settings.launcher.proxy.socks=Socks
|
||||
settings.launcher.proxy.socks=SOCKS
|
||||
settings.launcher.proxy.username=Username
|
||||
settings.launcher.theme=Theme
|
||||
settings.launcher.title_transparent=Transparent Titlebar
|
||||
@@ -1265,7 +1277,6 @@ settings.memory.lower_bound=Minimum Memory
|
||||
settings.memory.used_per_total=%1$.1f GB Used / %2$.1f GB Total
|
||||
settings.physical_memory=Physical Memory Size
|
||||
settings.show_log=Show Logs
|
||||
settings.skin=Skins for offline accounts are now supported. You can navigate to your account list to change your skin and cape, but other players cannot see it in multiplayer.
|
||||
settings.tabs.installers=Loaders
|
||||
settings.take_effect_after_restart=Applies After Restart
|
||||
settings.type=Settings Type of Instance
|
||||
@@ -1289,12 +1300,12 @@ update=Update
|
||||
update.accept=Update
|
||||
update.changelog=Changelog
|
||||
update.channel.dev=Beta
|
||||
update.channel.dev.hint=You are currently using a Beta channel build of the launcher, which may include some extra features but is also sometimes more unstable than the Release channel.\n\
|
||||
update.channel.dev.hint=You are currently using a Beta channel build of the launcher. While it may include some extra features, it is also sometimes less stable than the Release channel builds.\n\
|
||||
\n\
|
||||
If you encounter any bugs or problems, please navigate to <a href="hmcl://settings/feedback">Settings → Feedback</a> page to submit your feedback.
|
||||
update.channel.dev.title=Beta Channel Notice
|
||||
update.channel.nightly=Nightly
|
||||
update.channel.nightly.hint=You are currently using a Nightly channel build of the launcher, which may include some extra features but is also always more unstable than the other channels.\n\
|
||||
update.channel.nightly.hint=You are currently using a Nightly channel build of the launcher. While it may include some extra features, it is also always less stable than the other channel builds.\n\
|
||||
\n\
|
||||
If you encounter any bugs or problems, please navigate to <a href="hmcl://settings/feedback">Settings → Feedback</a> page to submit your feedback.
|
||||
update.channel.nightly.title=Nightly Channel Notice
|
||||
@@ -1347,10 +1358,14 @@ version.manage.remove_assets=Delete All Assets
|
||||
version.manage.remove_libraries=Delete All Libraries
|
||||
version.manage.rename=Rename Instance
|
||||
version.manage.rename.message=Enter New Instance Name
|
||||
version.manage.rename.fail=Failed to rename the instance, some files might be in use, or the name contains an invalid character.
|
||||
version.manage.rename.fail=Failed to rename the instance. Some files might be in use, or the name contains an invalid character.
|
||||
version.settings=Settings
|
||||
version.update=Update Modpack
|
||||
|
||||
wiki.tooltip=Minecraft Wiki Page
|
||||
wiki.version.game.release=https://minecraft.wiki/w/Java_Edition_%s
|
||||
wiki.version.game.snapshot=https://minecraft.wiki/w/Java_Edition_%s
|
||||
|
||||
wizard.prev=< Prev
|
||||
wizard.failed=Failed
|
||||
wizard.finish=Finish
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
# 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
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
about=について
|
||||
about.copyright=著作権
|
||||
about.copyright.statement=Copyright © 2024 huangyuhui
|
||||
about.copyright.statement=Copyright © 2025 huangyuhui
|
||||
about.author=著者
|
||||
about.author.statement=bilibili @huanghongxun
|
||||
about.claim=EULA
|
||||
@@ -283,6 +283,8 @@ curse.sort.name=名前
|
||||
curse.sort.popularity=人気
|
||||
curse.sort.total_downloads=合計ダウンロード数
|
||||
|
||||
datetime.format=yyyy/MM/dd H:mm:ss
|
||||
|
||||
download=ダウンロード
|
||||
download.hint=ゲームや modpack をインストールするか、mod、リソース パック、マップをダウンロードします
|
||||
download.code.404=リモートサーバーにファイルが見つかりません:%s
|
||||
@@ -535,7 +537,8 @@ launcher.agreement.hint=このソフトウェアを使用するには、EULAに
|
||||
launcher.background=背景画像
|
||||
launcher.background.classic=クラシック
|
||||
launcher.background.choose=背景画像ファイルを選択してください
|
||||
launcher.background.default=標準(ランチャーと同じディレクトリにある background.png/jpg/gif と bg フォルダから自動的に画像を取得します。)
|
||||
launcher.background.default=標準
|
||||
launcher.background.default.tooltip=ランチャーと同じディレクトリにある background.png/.jpg/.gif/.webp と bg フォルダから自動的に画像を取得します。
|
||||
launcher.background.network=ネットワーク
|
||||
launcher.background.translucent=半透明
|
||||
launcher.cache_directory=キャッシュ用のディレクトリ
|
||||
@@ -665,7 +668,8 @@ mods.add.success=mods %s が正常に追加されました。
|
||||
mods.category=Category
|
||||
mods.check_updates=更新を確認
|
||||
mods.check_updates.current_version=Current
|
||||
mods.check_updates.failed=一部のファイルのダウンロードに失敗しました
|
||||
mods.check_updates.failed_check=更新のチェックに失敗しました
|
||||
mods.check_updates.failed_download=一部のファイルのダウンロードに失敗しました
|
||||
mods.check_updates.file=ファイル
|
||||
mods.check_updates.source=Source
|
||||
mods.check_updates.target_version=Target
|
||||
@@ -715,7 +719,6 @@ world.name=マップ名
|
||||
world.name.enter=マップ名を入力してください
|
||||
world.reveal=フォルダを表示
|
||||
world.show_all=すべて表示
|
||||
world.time=EEE、MMM d、yyyy HH:mm:ss
|
||||
|
||||
profile=ゲームディレクトリ
|
||||
profile.already_exists=この名前はすでに存在します。別の名前を使用してください。
|
||||
@@ -855,7 +858,7 @@ settings.launcher.proxy.http=HTTP
|
||||
settings.launcher.proxy.none=プロキシなし
|
||||
settings.launcher.proxy.password=パスワード
|
||||
settings.launcher.proxy.port=ポート
|
||||
settings.launcher.proxy.socks=Socks
|
||||
settings.launcher.proxy.socks=SOCKS
|
||||
settings.launcher.proxy.username=アカウント
|
||||
settings.launcher.theme=テーマ
|
||||
settings.launcher.title_transparent=タイトルの透明性
|
||||
@@ -871,7 +874,6 @@ settings.memory.lower_bound=最小メモリ
|
||||
settings.memory.used_per_total=%1$.1f GB使用済み/%2$.1fGB合計
|
||||
settings.physical_memory=物理メモリサイズ
|
||||
settings.show_log=ログを表示
|
||||
settings.skin=オフラインアカウントのスキンの変更がサポートされるようになりました!アカウントページに移動して、今すぐ肌を変えてください!
|
||||
settings.tabs.installers=Installers
|
||||
settings.take_effect_after_restart=再起動後に有効になります
|
||||
settings.type=バージョン設定タイプ
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
# 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
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
about=О лаунчере
|
||||
about.copyright=Авторские права
|
||||
about.copyright.statement=Авторские права © 2024 huangyuhui.
|
||||
about.copyright.statement=Авторские права © 2025 huangyuhui.
|
||||
about.author=Автор
|
||||
about.author.statement=bilibili @huanghongxun
|
||||
about.claim=EULA
|
||||
@@ -50,33 +50,35 @@ about.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL)
|
||||
|
||||
account=Аккаунты
|
||||
account.cape=Плащ
|
||||
account.character=Персонаж
|
||||
account.choose=Выберите персонажа
|
||||
account.character=Игрок
|
||||
account.choose=Выберите игрока
|
||||
account.create=Добавить аккаунт
|
||||
account.create.microsoft=Добавить аккаунт Microsoft
|
||||
account.create.offline=Добавить автономный аккаунт
|
||||
account.create.authlibInjector=Создайте аккаунт authlib-injector
|
||||
account.create.offline=Добавить аккаунт в режиме офлайн
|
||||
account.create.authlibInjector=Добавить аккаунт authlib-injector
|
||||
account.email=Почта
|
||||
account.failed=Не удалось проверить аккаунт
|
||||
account.failed.character_deleted=Персонаж уже был удалён.
|
||||
account.failed=Не удалось обновить аккаунт.
|
||||
account.failed.character_deleted=Игрок уже был удален.
|
||||
account.failed.connect_authentication_server=Не удаётся связаться с серверами авторизации, проверьте свой интернет.
|
||||
account.failed.connect_injector_server=Не удаётся подключиться к серверу авторизации. Проверьте свой интернет и убедитесь, что вы ввели правильный URL-адрес.
|
||||
account.failed.injector_download_failure=Не удаётся скачать authlib-injector. Проверьте свой интернет или попробуйте переключиться на другое зеркало скачивания.
|
||||
account.failed.invalid_credentials=Неверный пароль или ограничена скорость, повторите попытку позже.
|
||||
account.failed.invalid_password=Неверный пароль
|
||||
account.failed.invalid_token=Попробуйте выйти из аккаунта и повторно войти введя пароль
|
||||
account.failed.invalid_token=Попробуйте выйти из аккаунта и повторно войти.
|
||||
account.failed.migration=Ваш аккаунт должен быть перенесён в Microsoft. Если вы уже сделали это, вам следует войти в свой перенесенный аккаунт Microsoft.
|
||||
account.failed.no_character=Аккаунт не имеет персонажей.
|
||||
account.failed.server_disconnected=Невозможно получить доступ к серверу авторизации, не удалось проверить информацию о аккаунте. \n\
|
||||
Вы можете выбрать «Повторить проверку аккаунта», чтобы попытаться снова. \n\
|
||||
Также можно выбрать «Пропустить проверку аккаунта», чтобы продолжить запуск игры, \n\
|
||||
но информация об аккаунте может быть не актуальной. Если срок действия информации \nо аккаунте истечёт, будет невозможно войти на сервер, который требует подтверждения \nаккаунта.
|
||||
account.failed.server_disconnected=Невозможно получить доступ к серверу авторизации, не удалось проверить информацию о аккаунте.\n\
|
||||
Вы можете выбрать «Повторить проверку аккаунта», чтобы попытаться снова.\n\
|
||||
Также можно выбрать «Пропустить проверку аккаунта», чтобы продолжить запуск игры,\n\
|
||||
но информация об аккаунте может быть не актуальной. Если срок действия информации\n\
|
||||
о аккаунте истечёт, будет невозможно войти на сервер, который требует подтверждения\n\
|
||||
аккаунта.
|
||||
account.failed.server_response_malformed=Неверный ответ сервера, видимо сервер авторизации не работает.
|
||||
account.failed.ssl=При подключении к серверу произошла ошибка SSL. Пожалуйста, попробуйте обновить Java.
|
||||
account.failed.wrong_account=Вы вошли в неверный аккаунт.
|
||||
account.hmcl.hint=Необходимо нажать на «Войти» и завершить процесс в открывшейся вкладке вашего браузера.
|
||||
# avoid too long sequence.
|
||||
account.injector.add=Новый сервер авторизации
|
||||
account.injector.empty=Ничего (Вы можете нажать на кнопку с плюсом справа, чтобы добавить)
|
||||
account.injector.empty=Ничего (Вы можете нажать кнопку «плюс», чтобы добавить еще)
|
||||
account.injector.http=Предупреждение\: Этот сервер использует небезопасный протокол HTTP, и любой игрок, находящийся между вашими соединениями, сможет увидеть ваши учётные данные в открытом виде.
|
||||
account.injector.link.homepage=Домашняя страница
|
||||
account.injector.link.register=Зарегистрироваться
|
||||
@@ -85,53 +87,78 @@ account.injector.server_url=URL-адрес сервера
|
||||
account.injector.server_name=Имя сервера
|
||||
account.login=Войти
|
||||
account.login.hint=Мы не храним ваш пароль.
|
||||
account.login.refresh=Повторный вход
|
||||
account.login.refresh.microsoft.hint=Поскольку авторизация учетной записи недействительна, вам необходимо повторно добавить свою учетную запись Microsoft
|
||||
account.login.skip=Войти в режиме офлайн
|
||||
account.login.retry=Повторить снова
|
||||
account.login.refresh=Войти снова
|
||||
account.login.refresh.microsoft.hint=Вам необходимо снова войти в аккаунт Microsoft, поскольку авторизация аккаунта недействительна.
|
||||
account.logout=Выйти
|
||||
account.register=Зарегестрироваться
|
||||
account.manage=Список аккаунтов
|
||||
account.copy_uuid=Копирование UUID
|
||||
account.methods=Способ входа
|
||||
account.methods.authlib_injector=authlib-injector
|
||||
account.methods.microsoft=Аккаунт Microsoft
|
||||
account.methods.microsoft.birth=Как изменить настройки дня рождения...
|
||||
account.methods.microsoft.deauthorize=Отменить авторизацию аккаунта
|
||||
account.methods.microsoft=Microsoft
|
||||
account.methods.microsoft.birth=Как изменить настройки даты рождения...
|
||||
account.methods.microsoft.close_page=Авторизация аккаунта Microsoft завершена. Осталось выполнить некоторые шаги, которые необходимо завершить позже. Вы можете закрыть эту страницу.
|
||||
account.methods.microsoft.deauthorize=Отменить авторизацию аккаунта
|
||||
account.methods.microsoft.error.add_family=Поскольку тебе ещё нет 18 лет, взрослый должен добавить тебя в семейный доступ, чтобы ты мог играть в Minecraft.
|
||||
account.methods.microsoft.error.add_family_probably=Проверьте в настройках своего аккаунта, исполнилось ли вам уже 18 лет или нет. Если нет, необходимо добавить семейный аккаунт.
|
||||
account.methods.microsoft.error.add_family_probably=Проверьте, что возраст, указанный в настройках вашего аккаунта, составляет не менее 18 лет. Если нет и вы считаете, что это ошибка, вы можете нажать «Как изменить настройки даты рождения...», чтобы узнать, как ее изменить.
|
||||
account.methods.microsoft.error.country_unavailable=Xbox Live недоступен в вашей стране/регионе.
|
||||
account.methods.microsoft.error.missing_xbox_account=Ваш аккаунт Microsoft не подключена к Xbox. Создайте его, прежде чем продолжить.
|
||||
account.methods.microsoft.error.no_character=На аккаунте отсутствует профиль Minecraft Java.\nФайл игры не может быть создан, пожалуйста,\nнажмите на ссылку выше, чтобы создать его
|
||||
account.methods.microsoft.error.no_character=Убедитесь, что вы приобрели Minecraft: Java Edition.\n\
|
||||
Если вы уже приобрели игру, профиль может быть еще не создан.\n\
|
||||
Нажмите «Создать профиль / Редактировать имя профиля», чтобы создать его.
|
||||
account.methods.microsoft.error.unknown=Не удалось войти. Microsoft отвечает с кодом ошибки %d.
|
||||
account.methods.microsoft.error.wrong_verify_method=Пожалуйста, войдите в систему, используя свой пароль на странице входа в аккаунт Microsoft, и не используйте проверочный код для авторизации.
|
||||
account.methods.microsoft.logging_in=Авторизация...
|
||||
account.methods.microsoft.makegameidsettings=Создать архив / изменение имени профиля
|
||||
account.methods.microsoft.hint=Вам необходимо нажать кнопку «Войти» и продолжить процесс авторизации во вновь открывшемся окне браузера.\nЕсли токен, используемый для входа в аккаунт Microsoft случайно утёк, вы можете нажать «Отменить авторизацию аккаунта» ниже, чтобы отменить авторизацию.\nЕсли у вас возникнут какие-либо проблемы, вы можете нажать кнопку справки в правом верхнем углу для получения помощи.
|
||||
account.methods.microsoft.manual=После нажатия кнопки «Войти», вы должны завершить авторизацию во вновь открывшемся окне браузера. Если окно браузера не открылось, вы можете щёлкнуть здесь, чтобы скопировать ссылку и вручную открыть её в браузере.\nЕсли токен, используемый для входа в аккаунт Microsoft случайно утёк, вы можете нажать «Отменить авторизацию аккаунта» ниже, чтобы отменить авторизацию.\n問題が発生した場合は、右上隅にあるヘルプ ボタンをクリックするとヘルプが表示されます。
|
||||
account.methods.microsoft.hint=Вам необходимо нажать кнопку «Войти» и продолжить процесс авторизации во вновь открывшемся окне браузера.\n\
|
||||
\n\
|
||||
Если токен, используемый для входа в аккаунт Microsoft случайно утёк, вы можете нажать «Отменить авторизацию аккаунта» ниже, чтобы отменить авторизацию.
|
||||
account.methods.microsoft.manual=Ваш код устройства - <b>%1$s</b>. Нажмите здесь, чтобы скопировать.\n\
|
||||
\n\
|
||||
После нажатия кнопки «Войти», вы должны завершить авторизацию во вновь открывшемся окне браузера. Если окно браузера не открылось, вы можете щёлкнуть здесь, чтобы скопировать ссылку и вручную открыть её в браузере.\n\
|
||||
\n\
|
||||
Если токен, используемый для входа в аккаунт Microsoft случайно утёк, вы можете нажать «Отменить авторизацию аккаунта» ниже, чтобы отменить авторизацию.
|
||||
account.methods.microsoft.makegameidsettings=Создать / редактировать профиль
|
||||
account.methods.microsoft.profile=Профиль аккаунта...
|
||||
account.methods.microsoft.purchase=Купить Minecraft
|
||||
account.methods.forgot_password=ЗАБЫЛ ПАРОЛЬ
|
||||
account.methods.microsoft.snapshot=Вы используете неофициальное построение HMCL, загрузите официальное построение для входа в Microsoft.
|
||||
account.methods.microsoft.snapshot=Вы используете неофициальную версию HMCL, загрузите официальную версию для входа в аккаунт Microsoft.
|
||||
account.methods.microsoft.snapshot.website=Официальный сайт
|
||||
account.methods.offline=Офлайн
|
||||
account.methods.offline.name.special_characters=Рекомендуется использовать только английские буквы, цифры и символы подчёркивания.
|
||||
account.methods.offline.name.invalid=Как правило, игровые имена пользователя ограничиваются английскими буквами, цифрами и символами подчёркивания и не могут превышать 16 символов.\n\
|
||||
\n\
|
||||
\ · Действительный: HuangYu, huang_Yu, Huang_Yu_123;\n\
|
||||
\ · Недействительно: Huang Yu, Huang-Yu_%%%, Huang_Yu_hello_world_hello_world.\n\
|
||||
\n\
|
||||
Если вы считаете, что на стороне сервера есть соответствующий мод или плагин, снимающий это ограничение, вы можете проигнорировать это предупреждение.
|
||||
account.methods.offline.uuid=UUID
|
||||
account.methods.offline.uuid.hint=UUID - это уникальный идентификатор игрового персонажа в Minecraft. Способ генерации UUID различается в разных лаунчерах игр. Изменение UUID на тот, который генерируется другим лаунчером гарантирует, что игровые блоки/предметы в рюкзаке вашего офлайн аккаунта останутся. Этот параметр предназначен для экспертов. Если вы не знаете что делаете, мы не советуем вам изменять этот параметр.\nЭта опция не требуется для присоединения к серверам.
|
||||
account.methods.offline.uuid.malformed=Malformed
|
||||
account.methods.offline.uuid.hint=UUID - это уникальный идентификатор игрового персонажа в Minecraft. Способ генерации UUID различается в разных лаунчерах игр. Изменение UUID на тот, который генерируется другим лаунчером гарантирует, что игровые блоки/предметы в рюкзаке вашего офлайн аккаунта останутся. Этот параметр предназначен для экспертов. Если вы не знаете что делаете, мы не советуем вам изменять этот параметр. Эта опция не требуется для присоединения к серверам.
|
||||
account.methods.offline.uuid.malformed=Недопустимый формат.
|
||||
account.methods.forgot_password=Забыли пароль
|
||||
account.missing=Нет аккаунтов
|
||||
account.missing.add=Нажмите здесь, чтобы добавить.
|
||||
account.move_to_global=Конвертировать в глобальный аккаунт\nИнформация об аккаунте будет сохранена в файле конфигурации в каталоге текущего пользователя системы.
|
||||
account.move_to_portable=Конвертировать в портативный аккаунт\nИнформация об аккаунте будет сохранена в файле конфигурации в той же папке, что и HMCL.
|
||||
account.not_logged_in=Вход не произведён
|
||||
account.password=Пароль
|
||||
account.portable=Портативный
|
||||
account.skin=Скин
|
||||
account.skin.file=Файл скина
|
||||
account.skin.model=Модель
|
||||
account.skin.model.default=Классическая
|
||||
account.skin.model.slim=Стройная
|
||||
account.skin.type.alex=Алекс
|
||||
account.skin.type.csl_api=Blessing Skin
|
||||
account.skin.type.csl_api.location=Адрес
|
||||
account.skin.type.csl_api.location.hint=URL-адрес CustomSkinAPI
|
||||
account.skin.type.little_skin=LittleSkin
|
||||
account.skin.type.little_skin.hint=Необходимо создать персонажа с тем же именем, что и на автономном аккаунте на сайте поставщика скинов. Затем ваш скин будет вашим загруженным скином на этот сайт.
|
||||
account.skin.type.little_skin.hint=Вам нужно создать игрока с тем же именем, что и у вашего офлайн-аукнта на сайте поставщика скинов. Теперь ваш скин будет установлен на скин, назначенный вашему игроку на сайте поставщика скинов.
|
||||
account.skin.type.local_file=Локальный файл скина
|
||||
account.skin.type.steve=Стив
|
||||
account.skin.upload=Загрузить скин
|
||||
account.skin.upload.failed=Не удаётся загрузить скин
|
||||
account.skin.invalid_skin=Недопустимый файл скина
|
||||
account.skin.upload.failed=Не удаётся загрузить скин.
|
||||
account.skin.invalid_skin=Недопустимый файл скина.
|
||||
account.username=Имя пользователя
|
||||
|
||||
archive.author=Автор(ы)
|
||||
@@ -295,6 +322,8 @@ curse.sort.name=Название
|
||||
curse.sort.popularity=Популярность
|
||||
curse.sort.total_downloads=Скачиваний
|
||||
|
||||
datetime.format=d MMM yyyy г., HH\:mm\:ss
|
||||
|
||||
download=Скачать
|
||||
download.hint=Установите игры и пакеты модов или загрузите моды, пакеты ресурсов и карты
|
||||
download.code.404=Файл не найден на удалённом сервере: %s
|
||||
@@ -471,7 +500,7 @@ install.installer.quilt=Quilt
|
||||
install.installer.quilt-api=QSL/QFAPI
|
||||
install.installer.version=%s
|
||||
install.modpack=Установить модпак
|
||||
install.new_game=Добавить новый экземпляр
|
||||
install.new_game=Установить сборку
|
||||
install.new_game.already_exists=Экземпляр уже существует. Используйте другое имя.
|
||||
install.new_game.current_game_version=Текущая версия экземпляра
|
||||
install.new_game.malformed=Недопустимое имя
|
||||
@@ -557,7 +586,8 @@ launcher.agreement.hint=Нужно принять пользовательско
|
||||
launcher.background=Фоновое изображение
|
||||
launcher.background.choose=Выберите фоновое изображение
|
||||
launcher.background.classic=Классическое
|
||||
launcher.background.default=По умолчанию (или background.png/jpg/gif, или папку с изображениями)
|
||||
launcher.background.default=По умолчанию
|
||||
launcher.background.default.tooltip=или background.png/.jpg/.gif/.webp, или папку с изображениями
|
||||
launcher.background.network=По ссылке
|
||||
launcher.background.translucent=Полупрозрачный
|
||||
launcher.cache_directory=Папка с кэшем
|
||||
@@ -596,17 +626,20 @@ message.success=Операция успешно завершена
|
||||
message.unknown=Неизвестно
|
||||
message.warning=Предупреждение
|
||||
|
||||
modpack=Модпак
|
||||
modpack=Модпаки
|
||||
modpack.choose=Выберите модпак
|
||||
modpack.choose.local=Импорт из файла
|
||||
modpack.choose.local.detail=Можно перетащить файл модпака сюда
|
||||
modpack.choose.remote=Скачать
|
||||
modpack.choose.remote.detail=Необходима прямая ссылка на скачивание модпака
|
||||
modpack.choose.remote.tooltip=Введите URL-адрес модпака
|
||||
modpack.choose.repository=Скачать модпак с CurseForge или Modrinth
|
||||
modpack.choose.repository.detail=Не забудьте вернуться на эту страницу и перетащить файл модпака сюда после того, как модпак будет скачан.
|
||||
modpack.completion=Скачивание зависимостей
|
||||
modpack.desc=Опишите модпак, включая введение и журнал изменений. Поддерживаются Markdown и изображения по URL-адресу.
|
||||
modpack.description=Описание
|
||||
modpack.download=Скачать модпак
|
||||
modpack.download.title=Скачать модпак - %1s
|
||||
modpack.enter_name=Введите имя модпака.
|
||||
modpack.export=Экспортировать модпак
|
||||
modpack.export.as=Экспортировать модпак как...
|
||||
@@ -615,20 +648,20 @@ modpack.files.blueprints=Чертежи BuildCraft
|
||||
modpack.files.config=Конфигурации модов
|
||||
modpack.files.dumps=Выходной файл отладки NEI
|
||||
modpack.files.hmclversion_cfg=Файл конфигурации лаунчера
|
||||
modpack.files.liteconfig=Файл конфигурации мода
|
||||
modpack.files.liteconfig=Связанные файлы LiteLoader
|
||||
modpack.files.mods=Моды
|
||||
modpack.files.mods.voxelmods=Параметры VoxelMods
|
||||
modpack.files.options_txt=Файл настроек Minecraft
|
||||
modpack.files.optionsshaders_txt=Файл настроек шейдера
|
||||
modpack.files.resourcepacks=Текстурпаки
|
||||
modpack.files.saves=Сохранения игры
|
||||
modpack.files.resourcepacks=Пакеты ресурсов/текстур
|
||||
modpack.files.saves=миры
|
||||
modpack.files.scripts=Файл конфигурации MineTweaker
|
||||
modpack.files.servers_dat=Файл списка серверов
|
||||
modpack.install=Установить модпак %s
|
||||
modpack.installing=Установка модпака
|
||||
modpack.introduction=Поддерживаются модпаки CurseForge, Modrinth, MultiMC и MCBBS.
|
||||
modpack.invalid=Неверный модпак, попробуйте скачать его заново.
|
||||
modpack.mismatched_type=Несоответствие типа модпака, текущий экземпляр имеет тип %s, но предоставленный экземпляр имеет тип %s.
|
||||
modpack.mismatched_type=Несоответствие типа модпака, текущий сборка имеет тип %s, но предоставленный сборка имеет тип %s.
|
||||
modpack.name=Имя модпака
|
||||
modpack.not_a_valid_name=Неверное имя модпака
|
||||
modpack.origin=Источник
|
||||
@@ -642,94 +675,168 @@ modpack.type.curse=Curse
|
||||
modpack.type.curse.error=Невозможно установить этот модпак. Повторите ещё раз.
|
||||
modpack.type.curse.not_found=Некоторые из необходимых ресурсов отсутствуют и поэтому не могут быть загружены. Скачайте последнюю версию или другие модпаки.
|
||||
modpack.type.manual.warning=Возможно, вам нужно напрямую распаковать файл этого модпака, а не импортировать его. И запустите игру с помощью прилагаемого лаунчера. Этот модпак создаётся вручную путем сжатия директории .minecraft, а не экспортируется лаунчером. HMCL может попытаться импортировать этот модпак, продолжать?
|
||||
modpack.type.mcbbs=Стандарт MCBBS
|
||||
modpack.type.mcbbs=MCBBS
|
||||
modpack.type.mcbbs.export=Может быть импортирован Hello Minecraft! Launcher
|
||||
modpack.type.modrinth=Modrinth
|
||||
modpack.type.multimc=MultiMC
|
||||
modpack.type.multimc.export=Может быть импортирован Hello Minecraft! Launcher и MultiMC
|
||||
modpack.type.server=Сервер автообновления модпака
|
||||
modpack.type.server.export=Разрешить менеджеру сервера удаленно обновлять клиент игры
|
||||
modpack.type.server.malformed=Некорректный манифест модпака сервера
|
||||
modpack.unsupported=Hello Minecraft! Launcher не поддерживает этот формат пакетов
|
||||
modpack.update=Обновить игру
|
||||
modpack.update=Обновление модпака
|
||||
modpack.wizard=Мастер экспорта модпака
|
||||
modpack.wizard.step.1=Основные параметры
|
||||
modpack.wizard.step.1.title=Установка основных параметров для модпака.
|
||||
modpack.wizard.step.1=Основные настройки
|
||||
modpack.wizard.step.1.title=Некоторые основные сведения о модпаке.
|
||||
modpack.wizard.step.2=Выбрать файлы
|
||||
modpack.wizard.step.2.title=Выберите файлы, которые вы хотите добавить в модпак.
|
||||
modpack.wizard.step.3=Тип модпака
|
||||
modpack.wizard.step.3.title=Выберите тип модпака, который вы хотите экспортировать.
|
||||
modpack.wizard.step.initialization.exported_version=Версия игры для экспорта
|
||||
modpack.wizard.step.initialization.force_update=Принудительное обновление модпака до последней версии (вам понадобится служба хостинга файлов)
|
||||
|
||||
modpack.wizard.step.initialization.include_launcher=Включать лаунчер
|
||||
modpack.wizard.step.initialization.save=Экспортировать в...
|
||||
modpack.wizard.step.initialization.warning=Перед созданием модпака, убедитесь что игра запускается, и это Release, а не Snapshot версия. Лаунчер сохранит настройки скачивания.\n\
|
||||
\n\
|
||||
Имейте в виду, что не разрешается добавлять моды и текстурпаки, в которых явно сказано, что они не должны распространяться или помещаться в модпаки.
|
||||
modpack.wizard.step.initialization.warning=Перед созданием модпака убедитесь, что игра запускается нормально и Minecraft является релизной версией, а не снапшотом. Пусковая установка сохранит ваши настройки скачивания.\n\
|
||||
\n\
|
||||
Помните, что вам не разрешается добавлять моды и пакет ресурсов, в которых явно указано, что они не подлежат распространению или помещению в модпак.
|
||||
modpack.wizard.step.initialization.server=Нажмите здесь для получения дополнительных руководств по созданию модпака на сервере, который может автообновляться.
|
||||
|
||||
modrinth.category.adventure=Adventure
|
||||
modrinth.category.adventure=Приключение
|
||||
modrinth.category.audio=Аудио
|
||||
modrinth.category.blocks=Блоки
|
||||
modrinth.category.bukkit=Bukkit
|
||||
modrinth.category.bungeecord=BungeeCord
|
||||
modrinth.category.challenging=Challenging
|
||||
modrinth.category.combat=Combat
|
||||
modrinth.category.challenging=Вызов
|
||||
modrinth.category.core-shaders=Ядро шейдеров
|
||||
modrinth.category.combat=Бой
|
||||
modrinth.category.cursed=Cursed
|
||||
modrinth.category.decoration=Decoration
|
||||
modrinth.category.economy=Economy
|
||||
modrinth.category.equipment=Equipment
|
||||
modrinth.category.decoration=Украшения
|
||||
modrinth.category.economy=Экономика
|
||||
modrinth.category.entities=Сущности
|
||||
modrinth.category.environment=Окружающая среда
|
||||
modrinth.category.equipment=Оборудование
|
||||
modrinth.category.fabric=Fabric
|
||||
modrinth.category.fonts=Шрифты
|
||||
modrinth.category.food=Еда
|
||||
modrinth.category.forge=Forge
|
||||
modrinth.category.game-mechanics=Игровая механика
|
||||
modrinth.category.gui=ГИП
|
||||
modrinth.category.items=Предметы
|
||||
modrinth.category.kitchen-sink=Kitchen-Sink
|
||||
modrinth.category.library=Библиотека
|
||||
modrinth.category.lightweight=Легкий
|
||||
modrinth.category.liteloader=LiteLoader
|
||||
modrinth.category.locale=Язык
|
||||
modrinth.category.magic=Магия
|
||||
modrinth.category.management=Управление
|
||||
modrinth.category.minecraft=Minecraft
|
||||
modrinth.category.minigame=Мини-игра
|
||||
modrinth.category.misc=Разное
|
||||
modrinth.category.mobs=Мобы
|
||||
modrinth.category.modded=Модифицированный
|
||||
modrinth.category.models=Модели
|
||||
modrinth.category.modloader=Мод-загрузчик
|
||||
modrinth.category.multiplayer=Сетевая игра
|
||||
modrinth.category.neoforge=NeoForge
|
||||
modrinth.category.optimization=Оптимизация
|
||||
modrinth.category.paper=Paper
|
||||
modrinth.category.purpur=Purpur
|
||||
modrinth.category.quests=Квесты
|
||||
modrinth.category.quilt=Quilt
|
||||
modrinth.category.realistic=Реалистичный
|
||||
modrinth.category.rift=Rift
|
||||
modrinth.category.simplistic=Упрощенный
|
||||
modrinth.category.social=Социальная
|
||||
modrinth.category.spigot=Spigot
|
||||
modrinth.category.sponge=Sponge
|
||||
modrinth.category.storage=Хранилище
|
||||
modrinth.category.technology=Технология
|
||||
modrinth.category.themed=Тематический
|
||||
modrinth.category.transportation=Транспорт
|
||||
modrinth.category.tweaks=Усовершенствование
|
||||
modrinth.category.utility=Утилиты
|
||||
modrinth.category.vanilla-like=Ванильное
|
||||
modrinth.category.velocity=Velocity
|
||||
modrinth.category.waterfall=Waterfall
|
||||
modrinth.category.worldgen=Генерация мира
|
||||
modrinth.category.datapack=Datapack
|
||||
modrinth.category.datapack=Набор данных
|
||||
modrinth.category.folia=Folia
|
||||
modrinth.category.8x-=8x-
|
||||
modrinth.category.16x=16x
|
||||
modrinth.category.32x=32x
|
||||
modrinth.category.48x=48x
|
||||
modrinth.category.64x=64x
|
||||
modrinth.category.128x=128x
|
||||
modrinth.category.256x=256x
|
||||
modrinth.category.512x+=512x+
|
||||
|
||||
mods=Моды
|
||||
mods.add=Добавить моды
|
||||
mods.add.failed=Не удаётся установить мод %s.
|
||||
mods.add.success=%s был успешно установлен.
|
||||
mods.add=Добавить мод
|
||||
mods.add.failed=Не удалось установить мод %s.
|
||||
mods.add.success=%s был успешно добавлен.
|
||||
mods.broken_dependency.title=Сломанная зависимость
|
||||
mods.broken_dependency.desc=Эта зависимость существовала раньше, но теперь ее нет. Попробуйте использовать другой источник скачивания.
|
||||
mods.category=Категория
|
||||
mods.channel.alpha=Альфа
|
||||
mods.channel.beta=Бета
|
||||
mods.channel.release=Релиз
|
||||
mods.check_updates=Проверить обновления
|
||||
mods.check_updates.current_version=Текущая версия
|
||||
mods.check_updates.failed=Не удаётся скачать некоторые файлы.
|
||||
mods.check_updates.empty=Все моды новейшие
|
||||
mods.check_updates.failed_check=Не удалось проверить обновления.
|
||||
mods.check_updates.failed_download=Не удалось скачать некоторые файлы.
|
||||
mods.check_updates.file=Файл
|
||||
mods.check_updates.source=Источник
|
||||
mods.check_updates.target_version=Целевая версия
|
||||
mods.check_updates.update=Обновить
|
||||
mods.choose_mod=Выберите мод
|
||||
mods.curseforge=CurseForge
|
||||
mods.dependency.embedded=Встроенные зависимости (Уже упакован в файл мода автором. Нет необходимости скачивать отдельно.)
|
||||
mods.dependency.optional=Необязательные зависимости (Если он отсутствует, игра будет работать нормально, но функции мода могут отсутствовать.)
|
||||
mods.dependency.required=Необходимые зависимости (Необходимо скачивать отдельно, отсутствие может помешать запуску игры.)
|
||||
mods.dependency.tool=Необходимые зависимости (Необходимо скачивать отдельно, отсутствие может помешать запуску игры.)
|
||||
mods.dependency.include=Встроенные зависимости (Уже упакован в файл мода автором. Нет необходимости скачивать отдельно.)
|
||||
mods.dependency.incompatible=Несовместимые моды (Одновременная установка этих модов не позволит запустить игру.)
|
||||
mods.dependency.broken=Сломанные зависимости (Этот мод существовал раньше, но теперь его нет. Попробуйте использовать другой источник скачивания.)
|
||||
mods.disable=Отключить
|
||||
mods.download=Скачать мод
|
||||
mods.download.title=Скачать мод - %1s
|
||||
mods.download.recommend=Рекомендуемая версия мода - Minecraft %1s
|
||||
mods.enable=Включить
|
||||
mods.manage=Управление модами
|
||||
mods.manage=Моды
|
||||
mods.mcbbs=MCBBS
|
||||
mods.mcmod=MCMOD
|
||||
mods.mcmod.page=Страница MCMOD
|
||||
mods.mcmod.search=Искать на MCMOD
|
||||
mods.mcmod=MCMod
|
||||
mods.mcmod.page=Страница MCMod
|
||||
mods.mcmod.search=Искать на MCMod
|
||||
mods.modrinth=Modrinth
|
||||
mods.name=Имя
|
||||
mods.not_modded=Для управления модами необходимо сначала установить загрузчик (Fabric, Forge или LiteLoader).\!
|
||||
mods.restore=Откат
|
||||
mods.name=Название
|
||||
mods.not_modded=Для управления модами необходимо сначала установить загрузчик (Forge, NeoForge, Fabric, Quilt или LiteLoader)!
|
||||
mods.restore=Восстановить
|
||||
mods.url=Официальная страница
|
||||
mods.update_modpack_mod.warning=Обновление модов в пакете модов может привести к непоправимым результатам, возможно, повредить пакет модов так, что он не сможет запуститься. Вы уверены, что хотите обновить?
|
||||
mods.install=Установить
|
||||
mods.save_as=Сохранить как
|
||||
|
||||
datapack=Датапаки
|
||||
nbt.entries=%s записи
|
||||
nbt.open.failed=Не удалось открыть файл
|
||||
nbt.save.failed=Не удалось сохранить файл
|
||||
nbt.title=Смотреть файл - %s
|
||||
|
||||
datapack=Наборы данных
|
||||
datapack.add=Установить
|
||||
datapack.choose_datapack=Выберите датапак для импорта
|
||||
datapack.extension=Датапак
|
||||
datapack.title=Датапаки мира %s
|
||||
datapack.choose_datapack=Выберите набор данных для импорта
|
||||
datapack.extension=Набор данных
|
||||
datapack.title=Мир [%s] - Наборы данных
|
||||
|
||||
web.failed=Не удалось загрузить страницу
|
||||
web.open_in_browser=Хотите ли вы открыть этот адрес в браузере?\n\n%s
|
||||
web.view_in_browser=Смотреть в браузере
|
||||
|
||||
world=Миры
|
||||
world.add=Добавить мир
|
||||
world.datapack=Управление датапаками
|
||||
world.datapack.1_13=Только Minecraft 1.13 или новее поддерживает датапаки.
|
||||
world.description=%s. Последний запуск игры %s. Версия игры\: %s.
|
||||
world.datapack=Управлять наборами данных
|
||||
world.datapack.1_13=Только Minecraft 1.13 или новее поддерживает наборы данных.
|
||||
world.description=%s. | Последний запуск игры %s. | Версия игры\: %s.
|
||||
world.download=Скачать мир
|
||||
world.export=Экспорт мира
|
||||
world.export.title=Выберите папку для экспорта мира
|
||||
@@ -738,51 +845,51 @@ world.export.wizard=Экспорт мира %s
|
||||
world.extension=Архив мира
|
||||
world.game_version=Версия игры
|
||||
world.import.already_exists=Мир уже существует.
|
||||
world.import.choose=Выберите архив сохранения, который хотите импортировать
|
||||
world.import.failed=Не удаётся импортировать этот мир\: %s
|
||||
world.import.invalid=Не удаётся разобрать сохранение.
|
||||
world.info.title=Сведения о мире %s
|
||||
world.import.choose=Выберите архив мира, который хотите импортировать
|
||||
world.import.failed=Не удалось импортировать этот мир\: %s
|
||||
world.import.invalid=Не удалось разобрать мир.
|
||||
world.info.title=Мир [%s] - Сведения
|
||||
world.info.basic=Основные сведения
|
||||
world.info.allow_cheats=Разрешить команды/читы
|
||||
world.info.dimension.the_nether=Нижний мир
|
||||
world.info.dimension.the_end=Край
|
||||
world.info.difficulty=Сложность
|
||||
world.info.difficulty.peaceful=Мирно
|
||||
world.info.difficulty.easy=Легко
|
||||
world.info.difficulty.normal=Обычно
|
||||
world.info.difficulty.hard=Тяжело
|
||||
world.info.difficulty.peaceful=Мирная
|
||||
world.info.difficulty.easy=Лёгкая
|
||||
world.info.difficulty.normal=Нормальная
|
||||
world.info.difficulty.hard=Сложная
|
||||
world.info.failed=Не удалось прочитать сведения о мире
|
||||
world.info.game_version=Версия игры
|
||||
world.info.last_played=Последний запуск игры
|
||||
world.info.generate_features=Генерировать структуры
|
||||
world.info.generate_features=Генерация строений
|
||||
world.info.player=Сведения о игроке
|
||||
world.info.player.food_level=Уровень голода
|
||||
world.info.player.game_type=Режим игры
|
||||
world.info.player.game_type.adventure=Приключение
|
||||
world.info.player.game_type.creative=Творческий
|
||||
world.info.player.game_type.spectator=Наблюдатель
|
||||
world.info.player.game_type.creative=Творчество
|
||||
world.info.player.game_type.spectator=Наблюдение
|
||||
world.info.player.game_type.survival=Выживание
|
||||
world.info.player.health=Здоровье
|
||||
world.info.player.last_death_location=Место последней смерти
|
||||
world.info.player.location=Расположение
|
||||
world.info.player.spawn=Место появления
|
||||
world.info.player.spawn=Точка возрождения
|
||||
world.info.player.xp_level=Уровень опыта
|
||||
world.info.random_seed=Сид
|
||||
world.info.random_seed=Ключ генератора мира
|
||||
world.info.time=Время игры
|
||||
world.info.time.format=%s дн.
|
||||
world.manage=Миры и датапаки
|
||||
world.manage=Миры
|
||||
world.name=Название мира
|
||||
world.name.enter=Введите название мира
|
||||
world.reveal=Показать в проводнике
|
||||
world.reveal=Показать в диспетчере файлов
|
||||
world.show_all=Показать все
|
||||
world.time=EEE, MMM d, yyyy HH\:mm\:ss
|
||||
|
||||
profile=Папки с играми
|
||||
profile.already_exists=Имя уже существует, используйте другое имя.
|
||||
profile.default=Текущая папка
|
||||
profile.home=По умолчанию
|
||||
profile.instance_directory=Папка с экземплярами
|
||||
profile.instance_directory.choose=Выберите папку для экземпляра
|
||||
profile.manage=Список папок с экземплярами
|
||||
profile.home=Лаунчер Minecraft
|
||||
profile.instance_directory=Папка с игрой
|
||||
profile.instance_directory.choose=Выберите папку с игрой
|
||||
profile.manage=Список папок для сборки
|
||||
profile.name=Имя
|
||||
profile.new=Новая папка
|
||||
profile.title=Папки с играми
|
||||
@@ -792,68 +899,77 @@ profile.use_relative_path=По возможности используйте о
|
||||
repositories.custom=Пользовательский репозиторий Maven (%s)
|
||||
repositories.maven_central=Универсальный (Maven Central)
|
||||
repositories.tencentcloud_mirror=Зеркало Mainland China (Репозиторий Tencent Cloud Maven)
|
||||
repositories.chooser=Для работы лаунчера требуется JavaFX.\n\
|
||||
\n\
|
||||
Нажмите «ОК», чтобы скачать JavaFX из указанного репозитория, или нажмите «Отмена» чтобы выйти.\n\
|
||||
\n\
|
||||
Репозитории\:
|
||||
repositories.chooser=Для работы HMCL требуется JavaFX.\n\
|
||||
\n\
|
||||
Нажмите «ОК», чтобы скачать JavaFX из указанного репозитория, или нажмите «Отмена» чтобы выйти.\n\
|
||||
\n\
|
||||
Репозитории:
|
||||
repositories.chooser.title=Выберите зеркало для скачивания JavaFX
|
||||
|
||||
resourcepack=Текстурпаки
|
||||
resourcepack=Пакеты ресурсов
|
||||
|
||||
search=Поиск
|
||||
search.hint.chinese=Поиск на китайском и английском языках
|
||||
search.hint.english=Поиск только на английском языке
|
||||
search.enter=Вводите здесь
|
||||
search.sort=Сортировать
|
||||
search.sort=Сортировка
|
||||
search.first_page=Первый
|
||||
search.previous_page=Пред.
|
||||
search.next_page=След.
|
||||
search.last_page=Последний
|
||||
search.page_n=%d / %s
|
||||
|
||||
selector.choose=Выбрать
|
||||
selector.choose=Выберите
|
||||
selector.choose_file=Выберите файл
|
||||
selector.custom=Свой
|
||||
|
||||
settings=Настройки
|
||||
|
||||
settings.advanced=Расширенные настройки
|
||||
settings.advanced.modify=Изменить расширенные настройки
|
||||
settings.advanced.title=Расширенные настройки - %s
|
||||
settings.advanced.custom_commands=Пользовательские команды
|
||||
settings.advanced.custom_commands.hint=Пользовательские команды выполняются со следующими переменными окружения:\n\
|
||||
\ - $INST_NAME: название версии\n\
|
||||
\ - $INST_ID: название версии\n\
|
||||
\ - $INST_DIR: абсолютный путь к версии\n\
|
||||
\ - $INST_MC_DIR: абсолютный путь к minecraft\n\
|
||||
\ - $INST_JAVA: двоичный файл java, используемый для запуска\n\
|
||||
\ - $INST_FORGE: устанавливается, если установлен Forge\n\
|
||||
\ - $INST_NEOFORGE: устанавливается, если установлен NeoForge\n\
|
||||
\ - $INST_LITELOADER: устанавливается, если установлен LiteLoader\n\
|
||||
\ - $INST_OPTIFINE: устанавливается, если установлен OptiFine\n\
|
||||
\ - $INST_FABRIC: устанавливается, если установлена Fabric\n\
|
||||
\ - $INST_QUILT: устанавливается, если установлена Quilt
|
||||
settings.advanced.dont_check_game_completeness=Не сканировать игровые файлы
|
||||
settings.advanced.dont_check_jvm_validity=Не проверять, может ли JVM запустить игру или нет
|
||||
settings.advanced.game_dir.default=По умолчанию (.minecraft/)
|
||||
settings.advanced.game_dir.independent=Раздельно (.minecraft/versions/<имя экземпляра>/, кроме assets и библиотек)
|
||||
settings.advanced.java_permanent_generation_space=Постоянное пространство
|
||||
\ · $INST_NAME: название сборки.\n\
|
||||
\ · $INST_ID: название сборки.\n\
|
||||
\ · $INST_DIR: абсолютный путь к рабочей папке сборки.\n\
|
||||
\ · $INST_MC_DIR: абсолютный путь к текущей папке игры.\n\
|
||||
\ · $INST_JAVA: двоичный файл java, используемый для запуска.\n\
|
||||
\ · $INST_FORGE: устанавливается, если установлен Forge.\n\
|
||||
\ · $INST_NEOFORGE: устанавливается, если установлен NeoForge.\n\
|
||||
\ · $INST_LITELOADER: устанавливается, если установлен LiteLoader.\n\
|
||||
\ · $INST_OPTIFINE: устанавливается, если установлен OptiFine.\n\
|
||||
\ · $INST_FABRIC: устанавливается, если установлен Fabric.\n\
|
||||
\ · $INST_QUILT: устанавливается, если установлен Quilt.
|
||||
settings.advanced.dont_check_game_completeness=Не проверять целостность игры
|
||||
settings.advanced.dont_check_jvm_validity=Не проверять совместимость Java VM
|
||||
settings.advanced.dont_patch_natives=Не пытайтесь автоматически заменить нативные библиотеки
|
||||
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.jvm=Параметры Java Virtual Machine
|
||||
settings.advanced.jvm=Настройки Java VM
|
||||
settings.advanced.jvm_args=Аргументы Java VM
|
||||
settings.advanced.jvm_args.prompt=- Если параметр, введённый в «Параметры Java Virtual Machine», совпадает\nс параметром по умолчанию, он не будет добавлен.\n\
|
||||
- Введите любые параметры GC в «Параметры Java Virtual Machine»,\nпараметр G1 по умолчанию будет отключен.\n\
|
||||
- Нажмите «Не добавлять аргументы JVM по умолчанию» ниже,\nчтобы запустить игру без добавления аргументов по умолчанию.
|
||||
settings.advanced.launcher_visibility.close=Закрывать после запуска игры.
|
||||
settings.advanced.launcher_visibility.hide=Сворачивать после запуска игры.
|
||||
settings.advanced.launcher_visibility.hide_and_reopen=Сворачивать и снова открывать, после закрытия игры.
|
||||
settings.advanced.launcher_visibility.keep=Оставлять видимым.
|
||||
settings.advanced.jvm_args.prompt=\ · Если параметр, введённый в «Аргументы Java VM», совпадает с параметром по умолчанию, он не будет добавлен.\n\
|
||||
\ · Введите любые параметры GC в «Аргументы Java VM», параметр G1 по умолчанию будет отключен.\n\
|
||||
\ · Нажмите «Не добавлять аргументы Java VM по умолчанию» ниже, чтобы запустить игру без добавления аргументов по умолчанию.
|
||||
settings.advanced.launcher_visibility.close=Закрывать после запуска игры
|
||||
settings.advanced.launcher_visibility.hide=Сворачивать после запуска игры
|
||||
settings.advanced.launcher_visibility.hide_and_reopen=Сворачивать и снова открывать, после закрытия игры
|
||||
settings.advanced.launcher_visibility.keep=Оставлять видимым
|
||||
settings.advanced.launcher_visible=Видимость лаунчера
|
||||
settings.advanced.minecraft_arguments=Аргументы запуска
|
||||
settings.advanced.minecraft_arguments.prompt=По умолчанию
|
||||
settings.advanced.natives_directory=Папка с нативными библиотеками
|
||||
settings.advanced.natives_directory.choose=Выберите расоложение нужных нативных библиотек
|
||||
settings.advanced.natives_directory.custom=Своя
|
||||
settings.advanced.natives_directory.custom=Пользовательское
|
||||
settings.advanced.natives_directory.default=По умолчанию
|
||||
settings.advanced.natives_directory.hint=Параметр предназначен только для пользователей Apple M1 или других официально не поддерживаемых платформ. Не изменяйте, если не знаете что делаете.\n\
|
||||
\n\
|
||||
Прежде чем продолжить, убедитесь что все библиотеки (например, lwjgl.dll, libopenal.so) находятся в нужной папке.
|
||||
settings.advanced.no_jvm_args=Не добавлять аргументы JVM по умолчанию
|
||||
settings.advanced.natives_directory.hint=Параметр предназначен только для пользователей Apple silicon или других официально не поддерживаемых платформ. Не изменяйте, если не знаете что делаете.\n\
|
||||
\n\
|
||||
Прежде чем продолжить, убедитесь что все библиотеки (например, lwjgl.dll, libopenal.so) находятся в нужной папке.\n\
|
||||
Примечание: Рекомендуется использовать полностью английские пути для указанных файлов локальных библиотек, иначе игра может не запуститься.
|
||||
settings.advanced.no_jvm_args=Не добавлять аргументы Java VM по умолчанию
|
||||
settings.advanced.precall_command=Команда перед запуском
|
||||
settings.advanced.precall_command.prompt=Команды, которые необходимо выполнить перед запуском игры
|
||||
settings.advanced.process_priority=Приоритет процесса
|
||||
@@ -864,6 +980,11 @@ settings.advanced.process_priority.above_normal=Выше обычного
|
||||
settings.advanced.process_priority.high=Высокий
|
||||
settings.advanced.post_exit_command=Команда после выхода
|
||||
settings.advanced.post_exit_command.prompt=Команды, которые необходимо выполнить после выхода из игры
|
||||
settings.advanced.renderer=Рендерер
|
||||
settings.advanced.renderer.default=OpenGL (По умолчанию)
|
||||
settings.advanced.renderer.d3d12=DirectX 12 (Низкая производительность и совместимость)
|
||||
settings.advanced.renderer.llvmpipe=ПО (Низкая производительность, лучшая совместимость)
|
||||
settings.advanced.renderer.zink=Vulkan (Лучшая производительность, низкая совместимость)
|
||||
settings.advanced.server_ip=Адрес сервера
|
||||
settings.advanced.server_ip.prompt=Присоединяться к серверу при запуске игры
|
||||
settings.advanced.use_native_glfw=[Только для Linux/FreeBSD] Использовать системный GLFW
|
||||
@@ -873,27 +994,30 @@ settings.advanced.workaround.warning=Варианты обхода предна
|
||||
settings.advanced.wrapper_launcher=Команда-оболочка
|
||||
settings.advanced.wrapper_launcher.prompt=Позволяет запускать с помощью дополнительной программы-оболочки, такой как «optirun» в Linux.
|
||||
|
||||
settings.custom=Своё
|
||||
settings.custom=Пользовательское
|
||||
|
||||
settings.game=Игровые настройки
|
||||
settings.game=Настройки
|
||||
settings.game.copy_global=Копировать из глобальные настройки
|
||||
settings.game.copy_global.copy_all=Копировать все
|
||||
settings.game.copy_global.copy_all.confirm=Вы уверены, что хотите переписать текущие настройки сборки? Это действие нельзя отменить!
|
||||
settings.game.current=Игра
|
||||
settings.game.dimension=Разрешение
|
||||
settings.game.exploration=Расположение
|
||||
settings.game.fullscreen=Полноэкранный режим
|
||||
settings.game.java_directory=Путь Java
|
||||
settings.game.java_directory=Java
|
||||
settings.game.java_directory.auto=Автовыбор
|
||||
settings.game.java_directory.auto.not_found=Совместимая версия Java не была установлена.
|
||||
settings.game.java_directory.bit=%s-разрядная
|
||||
settings.game.java_directory.choose=Выбрать папку с Java.
|
||||
settings.game.java_directory.invalid=Неверныая папка Java.
|
||||
settings.game.java_directory.choose=Выбрать папку с Java
|
||||
settings.game.java_directory.invalid=Неверныая папка Java
|
||||
settings.game.java_directory.version=Укажите версию Java
|
||||
settings.game.java_directory.template=%s (%s)
|
||||
settings.game.management=Инструменты
|
||||
settings.game.working_directory=Расположение папки
|
||||
settings.game.working_directory.choose=Выбор расположения
|
||||
settings.game.working_directory.hint=Включите параметр «Раздельно» в разделе «Расположение папки», чтобы позволить текущему экземпляру хранить свои настройки, сохранения и моды в отдельной папке.\n\
|
||||
\n\
|
||||
Рекомендуется включить этот параметр, чтобы избежать конфликтов с модами, но нужно будет переместить свои сохранения вручную.
|
||||
|
||||
\n\
|
||||
Рекомендуется включить этот параметр, чтобы избежать конфликтов с модами, но нужно будет переместить свои сохранения вручную.
|
||||
|
||||
settings.icon=Значок
|
||||
|
||||
@@ -912,7 +1036,7 @@ settings.launcher.font=Шрифт
|
||||
settings.launcher.general=Общие
|
||||
settings.launcher.language=Язык
|
||||
settings.launcher.launcher_log.export=Экспорт логов лаунчера
|
||||
settings.launcher.launcher_log.export.failed=Не удаётся экспортировать логи
|
||||
settings.launcher.launcher_log.export.failed=Не удалось экспортировать логи
|
||||
settings.launcher.launcher_log.export.success=Логи экспортированы в %s
|
||||
settings.launcher.log=Запись логов
|
||||
settings.launcher.log.font=Шрифт
|
||||
@@ -940,16 +1064,15 @@ settings.memory.lower_bound=Мин. объём памяти
|
||||
settings.memory.used_per_total=Использовано %1$.1f ГБ / Всего %2$.1f ГБ
|
||||
settings.physical_memory=Размер физической памяти
|
||||
settings.show_log=Показать логи
|
||||
settings.skin=Теперь поддерживаются скины для автономных аккаунтов. Можно зайти в настройки аккаунта, чтобы изменить свой скин и плащ, но другие игроки не смогут увидеть их в мультиплеере.
|
||||
settings.tabs.installers=Загрузчик модов/OptiFine
|
||||
settings.tabs.installers=Погрузчики
|
||||
settings.take_effect_after_restart=Применится после перезапуска
|
||||
settings.type=Тип настроек экземпляра
|
||||
settings.type.global=Общие настройки экземпляра (совместно используется экземплярами)
|
||||
settings.type.global.manage=Общие настройки игры
|
||||
settings.type.global.edit=Изменить глобальные настройки экземпляра
|
||||
settings.type.special.enable=Включить раздельные настройки для экземпляров
|
||||
settings.type.special.edit=Изменить настройки текущего экземпляра
|
||||
settings.type.special.edit.hint=В текущем экземпляре [%s] включены раздельные настройки для экземпляров, параметры на этой странице НЕ повлияют на этот экземпляр. Нажмите здесь, чтобы изменить раздельные параметры.
|
||||
settings.type.global=Глобальные настройки (совместно используется экземплярами)
|
||||
settings.type.global.manage=Глобальные настройки
|
||||
settings.type.global.edit=Изменить глобальные настройки
|
||||
settings.type.special.enable=Включить раздельные настройки для сборки
|
||||
settings.type.special.edit=Изменить текущие настройки сборки
|
||||
settings.type.special.edit.hint=В текущей сборке «%s» включен параметр «Специфические для сборки настройки». Все настройки на этой странице НЕ влияют на эту сборку. Нажмите здесь, чтобы изменить его настройки.
|
||||
|
||||
sponsor=Спонсоры
|
||||
sponsor.bmclapi=Загрузки для материкового Китая предоставляются BMCLAPI. Нажмите здесь для получения подробностей.
|
||||
@@ -958,6 +1081,8 @@ sponsor.hmcl=Hello Minecraft! Launcher — лаунчер Minecraft с откр
|
||||
system.architecture=Архитектура
|
||||
system.operating_system=Операционная система
|
||||
|
||||
unofficial.hint=Вы используете неофициальную версию HMCL. Мы не можем гарантировать безопасность.
|
||||
|
||||
update=Обновление
|
||||
update.accept=Обновить
|
||||
update.changelog=Изменения
|
||||
@@ -980,49 +1105,54 @@ update.bubble.title=Доступно обновление: %s
|
||||
update.bubble.subtitle=Кликните здесь для обновления
|
||||
update.note=Предупреждение: Бета-версии могут иметь больше функциональных возможностей и исправлений ошибок, но также и больше возможных ошибок.
|
||||
update.latest=Это последняя версия.
|
||||
update.no_browser=Не удаётся открыть браузер. Ссылка была скопирована в буфер обмена. Вставьте его в адресную строку браузера для обновления.
|
||||
update.no_browser=Не удалось открыть браузер. Ссылка была скопирована в буфер обмена. Вставьте его в адресную строку браузера для обновления.
|
||||
update.tooltip=Обновить
|
||||
|
||||
version=Игры
|
||||
version.name=Название сборки
|
||||
version.cannot_read=Невозможно найти версию игры. Невозможно продолжить автоматическую установку.
|
||||
version.empty=Нет игр
|
||||
version.empty.add=Установить новую версию
|
||||
version.empty=Нет сборок
|
||||
version.empty.add=Установить новую сборок
|
||||
version.empty.launch=Нет версии для запуска, установите версию через страницу скачивания.
|
||||
version.empty.hint=Нет установленной версии игры. Кликните здесь, чтобы скачать новую игру.
|
||||
version.game.old=Старые
|
||||
version.game.old=Исторические
|
||||
version.game.release=Релиз
|
||||
version.game.releases=Релизы
|
||||
version.game.snapshot=Снапшот
|
||||
version.game.snapshots=Снапшоты
|
||||
version.launch=Играть
|
||||
version.launch.test=Тестовая игра
|
||||
version.switch=Сменить версию
|
||||
version.switch=Сменить сборку
|
||||
version.launch_script=Создать сценарий запуска
|
||||
version.launch_script.failed=Невозможно выполнить сценарий запуска.
|
||||
version.launch_script.failed=Невозможно создать сценарий запуска.
|
||||
version.launch_script.save=Сохранить сценарий запуска
|
||||
version.launch_script.success=Создан сценарий %s.
|
||||
version.manage=Все версии
|
||||
version.manage.clean=Очистить каталог игры
|
||||
version.manage.clean.tooltip=Очистить логи, отчёты о сбоях
|
||||
version.manage.duplicate=Скопировать экземпляр игры
|
||||
version.manage.duplicate.duplicate_save=Скопировать сохранения
|
||||
version.manage.duplicate.prompt=Введите название новой версии
|
||||
version.manage=Все сборки
|
||||
version.manage.clean=Очистить журнал игры
|
||||
version.manage.clean.tooltip=Очистить файлы в папках «logs» и «crash-reports».
|
||||
version.manage.duplicate=Скопировать экземпляр сборки
|
||||
version.manage.duplicate.duplicate_save=Скопировать миры
|
||||
version.manage.duplicate.prompt=Введите новое название для этой сборки
|
||||
version.manage.duplicate.confirm=Новая версия-копия, будет заблокирована: принудительный независимый режим, независимые игровые настройки.
|
||||
version.manage.manage=Управление версией
|
||||
version.manage.manage.title=Управление версией - %1s
|
||||
version.manage.manage=Изменить сборку
|
||||
version.manage.manage.title=Изменить сборку - %1s
|
||||
version.manage.redownload_assets_index=Обновить файлы игровых активов
|
||||
version.manage.remove=Удалить
|
||||
version.manage.remove.confirm=Удалить %s? Вы не сможете восстановить эту версию снова!
|
||||
version.manage.remove.confirm.trash=Удалить %s? Вы можете восстановить эту версию с именем %s из корзины системы.
|
||||
version.manage.remove.confirm.independent=Поскольку эта версия находится в независимом режиме, удаление этой версии также приведёт к удалению всех сохранённых миров, принадлежащих этой версии. Удалить %s?
|
||||
version.manage.remove.confirm=Удалить %s? Вы не сможете восстановить эту сборку снова!
|
||||
version.manage.remove.confirm.trash=Удалить %s? Вы можете восстановить эту сборку с именем %s из корзины системы.
|
||||
version.manage.remove.confirm.independent=Поскольку эта сборка находится в режиме разделения, удаление этой сборки приведет к удалению всех миров, принадлежащих этой сборке. Удалить %s?
|
||||
version.manage.remove_assets=Удалить файлы игровых активов
|
||||
version.manage.remove_libraries=Удалить файлы библиотек
|
||||
version.manage.rename=Переименовать
|
||||
version.manage.rename.message=Введите новое название для этой версии
|
||||
version.manage.rename.fail=Не удалось переименовать версию.
|
||||
version.manage.rename.message=Введите новое название для этой сборки
|
||||
version.manage.rename.fail=Не удалось переименовать сборку. Возможно, некоторые файлы уже используются или в их имени содержится недопустимый символ.
|
||||
version.settings=Настройки
|
||||
version.update=Обновить модпак
|
||||
|
||||
wiki.tooltip=Страница Minecraft Wiki
|
||||
wiki.version.game.release=https://ru.minecraft.wiki/w/%s_(Java_Edition)
|
||||
wiki.version.game.snapshot=https://ru.minecraft.wiki/w/%s_(Java_Edition)
|
||||
|
||||
wizard.prev=< Пред.
|
||||
wizard.failed=Не удалось
|
||||
wizard.finish=Завершено
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
# 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
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
about=關於
|
||||
about.copyright=著作權
|
||||
about.copyright.statement=著作權所有 © 2024 huangyuhui
|
||||
about.copyright.statement=著作權所有 © 2025 huangyuhui
|
||||
about.author=作者
|
||||
about.author.statement=bilibili @huanghongxun
|
||||
about.claim=使用者協議
|
||||
@@ -148,12 +148,14 @@ account.skin.file=外觀圖片檔案
|
||||
account.skin.model=模型
|
||||
account.skin.model.default=寬型
|
||||
account.skin.model.slim=纖細
|
||||
account.skin.type.alex=Alex
|
||||
account.skin.type.csl_api=Blessing Skin 伺服器
|
||||
account.skin.type.csl_api.location=伺服器位址
|
||||
account.skin.type.csl_api.location.hint=CustomSkinAPI 位址
|
||||
account.skin.type.little_skin=LittleSkin 皮膚站
|
||||
account.skin.type.little_skin.hint=你需要在皮膚站中加入並使用和該離線帳戶同名角色,此時離線帳戶外觀將為皮膚站上對應角色所設定的外觀。
|
||||
account.skin.type.local_file=本機外觀圖片檔案
|
||||
account.skin.type.steve=Steve
|
||||
account.skin.upload=上傳/編輯外觀
|
||||
account.skin.upload.failed=外觀上傳失敗
|
||||
account.skin.invalid_skin=無法識別的外觀檔案
|
||||
@@ -319,6 +321,8 @@ curse.sort.name=名稱
|
||||
curse.sort.popularity=熱度
|
||||
curse.sort.total_downloads=下載量
|
||||
|
||||
datetime.format=yyyy 年 MM 月 dd 日 HH:mm:ss
|
||||
|
||||
download=下載
|
||||
download.hint=安裝遊戲和模組包或下載模組、資源包和地圖
|
||||
download.code.404=遠端伺服器沒有需要下載的檔案:%s
|
||||
@@ -328,7 +332,7 @@ download.existing=檔案已存在,無法儲存。你可以將檔案儲存至
|
||||
download.external_link=打開下載網站
|
||||
download.failed=下載失敗:%1$s\n錯誤碼:%2$d
|
||||
download.failed.empty=沒有可供安裝的版本,按一下此處返回。
|
||||
download.failed.no_code=下載失敗:%s
|
||||
download.failed.no_code=下載失敗
|
||||
download.failed.refresh=載入版本清單失敗,按一下此處重試。
|
||||
download.game=新遊戲
|
||||
download.provider.bmclapi=BMCLAPI (bangbang93, https://bmclapi2.bangbang93.com/)
|
||||
@@ -510,6 +514,7 @@ install.installer.quilt-api=QSL/QFAPI
|
||||
install.installer.version=%s
|
||||
install.installer.external_version=%s [由外部安裝的版本,無法解除安裝或更換]
|
||||
install.modpack=安裝模組包
|
||||
install.name.invalid=名稱中包含非 ASCII 字元(如 Emoji 表情或中文字元)。\n建議修改名稱,名稱建議僅包含英文字母、數字和底線,以防啟動遊戲時出現問題。是否繼續安裝?
|
||||
install.new_game=安裝新實例
|
||||
install.new_game.already_exists=此實例已經存在,請重新命名
|
||||
install.new_game.current_game_version=目前遊戲版本
|
||||
@@ -525,6 +530,7 @@ java.disabled.management=管理已禁用的 Java
|
||||
java.disabled.management.remove=從清單中移除此 Java
|
||||
java.disabled.management.restore=重新啟用此 Java
|
||||
java.download=下載 Java
|
||||
java.download.banshanjdk-8=下載 Banshan JDK 8
|
||||
java.download.load_list.failed=載入版本清單失敗
|
||||
java.download.more=更多發行版
|
||||
java.download.prompt=請選擇你要下載的 Java 版本:
|
||||
@@ -602,7 +608,8 @@ launcher.agreement.hint=同意本軟體的使用者協議與免責宣告以使
|
||||
launcher.background=背景圖片
|
||||
launcher.background.choose=選取背景圖片
|
||||
launcher.background.classic=經典
|
||||
launcher.background.default=預設 (自動尋找啟動器同目錄下的「background.png/.jpg/.gif」及「bg」目錄內的圖片)
|
||||
launcher.background.default=預設
|
||||
launcher.background.default.tooltip=自動尋找啟動器同目錄下的「background.png/.jpg/.gif/.webp」及「bg」目錄內的圖片
|
||||
launcher.background.network=網路
|
||||
launcher.background.translucent=半透明
|
||||
launcher.cache_directory=檔案下載快取目錄
|
||||
@@ -625,7 +632,7 @@ logwindow.terminate_game=結束遊戲處理程式
|
||||
logwindow.title=日誌
|
||||
logwindow.help=你可以前往 HMCL 社群,向他人尋求幫助
|
||||
logwindow.autoscroll=自動滾動
|
||||
logwindow.export_game_crash_logs=匯出遊戲崩潰訊息
|
||||
logwindow.export_game_crash_logs=匯出遊戲崩潰資訊
|
||||
logwindow.export_dump=匯出遊戲執行堆疊
|
||||
logwindow.export_dump.no_dependency=你的 Java 不包含用於建立遊戲執行堆疊的相依元件。請前往 Discord 或 HMCL QQ 群尋求幫助。
|
||||
|
||||
@@ -657,6 +664,7 @@ modpack.completion=下載模組包相關檔案
|
||||
modpack.desc=描述你要製作的模組包,比如模組包注意事項和更新紀錄,支援 Markdown (圖片請上傳至網路)。
|
||||
modpack.description=模組包描述
|
||||
modpack.download=下載模組包
|
||||
modpack.download.title=模組包下載 - %1s
|
||||
modpack.enter_name=給遊戲取個你喜歡的名稱
|
||||
modpack.export=匯出模組包
|
||||
modpack.export.as=請選取模組包類型。若你無法決定,請選取 MCBBS 類型。
|
||||
@@ -790,7 +798,8 @@ mods.channel.release=Release
|
||||
mods.check_updates=檢查模組更新
|
||||
mods.check_updates.current_version=目前版本
|
||||
mods.check_updates.empty=沒有需要更新的模組
|
||||
mods.check_updates.failed=部分檔案下載失敗
|
||||
mods.check_updates.failed_check=檢查更新失敗
|
||||
mods.check_updates.failed_download=部分檔案下載失敗
|
||||
mods.check_updates.file=檔案
|
||||
mods.check_updates.source=來源
|
||||
mods.check_updates.target_version=目標版本
|
||||
@@ -888,7 +897,6 @@ world.name=世界名稱
|
||||
world.name.enter=輸入世界名稱
|
||||
world.reveal=開啟目錄
|
||||
world.show_all=全部顯示
|
||||
world.time=yyyy 年 M 月 d 日, HH:mm:ss
|
||||
|
||||
profile=遊戲目錄
|
||||
profile.already_exists=該名稱已存在
|
||||
@@ -972,7 +980,7 @@ settings.advanced.natives_directory.hint=本選項提供給 Apple M1 等未受
|
||||
settings.advanced.no_jvm_args=不加入預設的 JVM 參數
|
||||
settings.advanced.precall_command=遊戲啟動前執行指令
|
||||
settings.advanced.precall_command.prompt=將在遊戲啟動前呼叫使用
|
||||
settings.advanced.process_priority=處理程序優先度
|
||||
settings.advanced.process_priority=處理程式優先度
|
||||
settings.advanced.process_priority.low=低 (節省遊戲占用資源,可能會造成遊戲卡頓)
|
||||
settings.advanced.process_priority.below_normal=較低 (節省遊戲占用資源,可能會造成遊戲卡頓)
|
||||
settings.advanced.process_priority.normal=中 (平衡)
|
||||
@@ -997,6 +1005,9 @@ settings.advanced.wrapper_launcher.prompt=如填寫「optirun」後,啟動指
|
||||
settings.custom=自訂
|
||||
|
||||
settings.game=遊戲設定
|
||||
settings.game.copy_global=複製全域遊戲設定
|
||||
settings.game.copy_global.copy_all=複製全部
|
||||
settings.game.copy_global.copy_all.confirm=你確定要覆寫目前實例特定遊戲設定嗎?該操作無法復原!
|
||||
settings.game.current=遊戲
|
||||
settings.game.dimension=遊戲介面解析度大小
|
||||
settings.game.exploration=瀏覽
|
||||
@@ -1044,7 +1055,7 @@ settings.launcher.proxy.http=HTTP
|
||||
settings.launcher.proxy.none=不使用代理
|
||||
settings.launcher.proxy.password=密碼
|
||||
settings.launcher.proxy.port=連線埠
|
||||
settings.launcher.proxy.socks=Socks
|
||||
settings.launcher.proxy.socks=SOCKS
|
||||
settings.launcher.proxy.username=帳戶
|
||||
settings.launcher.theme=主題
|
||||
settings.launcher.title_transparent=標題欄透明
|
||||
@@ -1061,7 +1072,6 @@ settings.memory.lower_bound=最低分配
|
||||
settings.memory.used_per_total=已使用 %1$.1f GB / 總記憶體 %2$.1f GB
|
||||
settings.physical_memory=實體記憶體大小
|
||||
settings.show_log=查看日誌
|
||||
settings.skin=現已支援離線帳戶更換外觀,你可以到帳戶頁面更改離線帳戶的外觀和披風 (多人遊戲下其他玩家無法看到你的外觀)。
|
||||
settings.tabs.installers=自動安裝
|
||||
settings.take_effect_after_restart=重啟後生效
|
||||
settings.type=實例遊戲設定類型
|
||||
@@ -1147,6 +1157,10 @@ version.manage.rename.fail=重新命名版本失敗,可能檔案被佔用或
|
||||
version.settings=遊戲設定
|
||||
version.update=更新模組包
|
||||
|
||||
wiki.tooltip=Minecraft Wiki 頁面
|
||||
wiki.version.game.release=https://zh.minecraft.wiki/w/Java版%s?variant=zh-tw
|
||||
wiki.version.game.snapshot=https://zh.minecraft.wiki/w/%s?variant=zh-tw
|
||||
|
||||
wizard.prev=< 上一步
|
||||
wizard.failed=失敗
|
||||
wizard.finish=完成
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
# 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
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
about=关于
|
||||
about.copyright=版权
|
||||
about.copyright.statement=版权所有 © 2024 huangyuhui
|
||||
about.copyright.statement=版权所有 © 2025 huangyuhui
|
||||
about.author=作者
|
||||
about.author.statement=bilibili @huanghongxun
|
||||
about.claim=用户协议
|
||||
@@ -157,12 +157,14 @@ account.skin.file=皮肤图片文件
|
||||
account.skin.model=模型
|
||||
account.skin.model.default=宽型
|
||||
account.skin.model.slim=纤细
|
||||
account.skin.type.alex=Alex
|
||||
account.skin.type.csl_api=Blessing Skin 服务器
|
||||
account.skin.type.csl_api.location=服务器地址
|
||||
account.skin.type.csl_api.location.hint=CustomSkinAPI 地址
|
||||
account.skin.type.little_skin=LittleSkin 皮肤站
|
||||
account.skin.type.little_skin.hint=你需要在皮肤站中创建并使用和该离线账户同名的角色,此时离线账户皮肤将显示为皮肤站上对应角色所设置的皮肤。\n你可以点击右上角帮助按钮进行求助。
|
||||
account.skin.type.local_file=本地皮肤图片文件
|
||||
account.skin.type.steve=Steve
|
||||
account.skin.upload=上传/编辑皮肤
|
||||
account.skin.upload.failed=皮肤上传失败
|
||||
account.skin.invalid_skin=无法识别的皮肤文件
|
||||
@@ -328,6 +330,8 @@ curse.sort.name=名称
|
||||
curse.sort.popularity=热度
|
||||
curse.sort.total_downloads=下载量
|
||||
|
||||
datetime.format=yyyy 年 MM 月 dd 日 HH:mm:ss
|
||||
|
||||
download=下载
|
||||
download.hint=安装游戏和整合包或下载模组、资源包和地图
|
||||
download.code.404=远程服务器不包含需要下载的文件: %s\n你可以点击右上角帮助按钮进行求助。
|
||||
@@ -337,7 +341,7 @@ download.existing=文件已存在,无法保存。你可以将文件保存至
|
||||
download.external_link=打开下载网站
|
||||
download.failed=下载失败: %1$s,\n错误码:%2$d\n你可以点击右上角帮助按钮进行求助。
|
||||
download.failed.empty=[没有可供安装的版本,点击此处返回]\n(你可以点击右上角帮助按钮进行求助)
|
||||
download.failed.no_code=下载失败: %s
|
||||
download.failed.no_code=下载失败
|
||||
download.failed.refresh=[加载版本列表失败,点击此处重试]\n(你可以点击右上角帮助按钮进行求助)
|
||||
download.game=新游戏
|
||||
download.provider.bmclapi=BMCLAPI (bangbang93, https://bmclapi2.bangbang93.com)
|
||||
@@ -434,7 +438,7 @@ game.crash.reason.jdk_9=当前游戏由于 Java 版本过高,无法继续运
|
||||
game.crash.reason.jvm_32bit=当前游戏由于内存分配过大,超过了 32 位 Java 内存限制,无法继续运行。\n如果你的电脑是 64 位系统,请下载安装并更换 64 位 Java。<a href="https://bell-sw.com/pages/downloads/#downloads">下载 Java</a>\n如果你的电脑是 32 位系统,你或许可以重新安装 64 位系统,或换一台新电脑。\n或者,你可以在“(全局/版本特定) 游戏设置 → 游戏内存”中关闭“自动分配内存”,并且把内存限制调节为 1024 MB 或以下。
|
||||
game.crash.reason.loading_crashed_forge=当前游戏由于模组“%1$s (%2$s)”错误,无法继续运行。\n你可以尝试删除或更新该模组以解决问题。
|
||||
game.crash.reason.loading_crashed_fabric=当前游戏由于模组“%1$s”错误,无法继续运行。\n你可以尝试删除或更新该模组以解决问题。
|
||||
game.crash.reason.memory_exceeded=当前游戏由于分配的内存过大,无法继续运行。\n该问题是由于系统页面文件太小导致的。\n你需要在全局(特定)游戏设置中关闭游戏内存的自动分配,并将游戏内存调低至游戏能正常启动为止。\n你还可以尝试将 虚拟内存 设置调整至「自动管理所有驱动器分页文件大小」,<a href="https://docs.hmcl.net/assets/img/hmcl/自动管理所有驱动器分页文件大小.webp">详情</a>。
|
||||
game.crash.reason.memory_exceeded=当前游戏由于分配的内存过大,无法继续运行。\n该问题是由于系统页面文件太小导致的。\n你需要在全局(特定)游戏设置中关闭游戏内存的自动分配,并将游戏内存调低至游戏能正常启动为止。\n你还可以尝试将虚拟内存设置调整为“自动管理所有驱动器分页文件大小”,<a href="https://docs.hmcl.net/assets/img/hmcl/自动管理所有驱动器分页文件大小.webp">详情</a>。
|
||||
game.crash.reason.mac_jdk_8u261=当前游戏由于你所使用的 Forge/OptiFine 与 Java 冲突而崩溃。\n请尝试更新 Forge/OptiFine,或使用 Java 8u251 及更早版本启动。
|
||||
game.crash.reason.mod=当前游戏由于“%1$s”的问题,无法继续运行。\n你可以更新或删除已经安装的“%1$s”再试。
|
||||
game.crash.reason.mod_resolution=当前游戏由于模组依赖问题,无法继续运行。Fabric 提供了如下信息:\n%1$s
|
||||
@@ -521,6 +525,7 @@ install.installer.quilt-api=QSL/QFAPI
|
||||
install.installer.version=%s
|
||||
install.installer.external_version=%s (由外部安装的版本,无法卸载或更换)
|
||||
install.modpack=安装整合包
|
||||
install.name.invalid=名称中包含非 ASCII 字符(如 Emoji 表情或中文字符)。\n建议修改名称,名称建议仅包含英文字母、数字和下划线,以防启动游戏时出现问题。是否继续安装?
|
||||
install.new_game=安装新游戏
|
||||
install.new_game.already_exists=此版本已经存在,请换一个名字
|
||||
install.new_game.current_game_version=当前游戏版本
|
||||
@@ -536,6 +541,7 @@ java.disabled.management=管理已禁用的 Java
|
||||
java.disabled.management.remove=从列表中移除此 Java
|
||||
java.disabled.management.restore=重新启用此 Java
|
||||
java.download=下载 Java
|
||||
java.download.banshanjdk-8=下载 Banshan JDK 8
|
||||
java.download.load_list.failed=加载版本列表失败
|
||||
java.download.more=更多发行版
|
||||
java.download.prompt=请选择你要下载的 Java 版本:
|
||||
@@ -561,7 +567,7 @@ java.uninstall.confirm=你确定要卸载此 Java 吗?此操作无法撤销!
|
||||
lang=简体中文
|
||||
lang.default=跟随系统语言
|
||||
|
||||
launch.advice==%s 是否继续启动?
|
||||
launch.advice=%s 是否继续启动?
|
||||
launch.advice.multi=检测到以下问题:\n\n%s\n\n这些问题可能导致游戏无法正常启动或影响游戏体验,是否继续启动?\n你可以点击右上角帮助按钮进行求助。
|
||||
launch.advice.java.auto=当前选择的 Java 虚拟机版本不满足游戏要求。\n点击“是”即可由 HMCL 来自动选取合适的 Java 虚拟机版本。\n或者你可以在“(全局/版本特定) 游戏设置 → 游戏 Java”中选择一个合适的 Java 虚拟机版本。
|
||||
launch.advice.java.modded_java_7=Minecraft 1.7.2 及更低版本需要 Java 7 及更低版本。
|
||||
@@ -613,7 +619,8 @@ launcher.agreement.hint=同意本软件的用户协议与免责声明以使用
|
||||
launcher.background=背景图片
|
||||
launcher.background.choose=选择背景图片
|
||||
launcher.background.classic=经典
|
||||
launcher.background.default=默认 (自动检索启动器同文件夹下的“background.png/.jpg/.gif”及“bg”文件夹内的图片)
|
||||
launcher.background.default=默认
|
||||
launcher.background.default.tooltip=自动检索启动器同文件夹下的“background.png/.jpg/.gif/.webp”及“bg”文件夹内的图片
|
||||
launcher.background.network=网络
|
||||
launcher.background.translucent=半透明
|
||||
launcher.cache_directory=文件下载缓存文件夹
|
||||
@@ -668,6 +675,7 @@ modpack.completion=下载整合包相关文件
|
||||
modpack.desc=描述你要制作的整合包,比如整合包注意事项和更新记录,支持 Markdown (图片请用网络链接)
|
||||
modpack.description=整合包描述
|
||||
modpack.download=下载整合包
|
||||
modpack.download.title=整合包下载 - %1s
|
||||
modpack.enter_name=给游戏起个你喜欢的名字
|
||||
modpack.export=导出整合包
|
||||
modpack.export.as=请选择整合包类型 (若无法决定,请选择“我的世界中文论坛整合包标准”)
|
||||
@@ -801,7 +809,8 @@ mods.channel.release=稳定版本
|
||||
mods.check_updates=检查模组更新
|
||||
mods.check_updates.current_version=当前版本
|
||||
mods.check_updates.empty=没有需要更新的模组
|
||||
mods.check_updates.failed=部分文件下载失败
|
||||
mods.check_updates.failed_check=检查更新失败
|
||||
mods.check_updates.failed_download=部分文件下载失败
|
||||
mods.check_updates.file=文件
|
||||
mods.check_updates.source=来源
|
||||
mods.check_updates.target_version=目标版本
|
||||
@@ -899,7 +908,6 @@ world.name=世界名称
|
||||
world.name.enter=输入世界名称
|
||||
world.reveal=打开文件夹
|
||||
world.show_all=显示全部
|
||||
world.time=yyyy 年 MM 月 dd 日, HH:mm:ss
|
||||
|
||||
profile=游戏文件夹
|
||||
profile.already_exists=该名称已存在
|
||||
@@ -1008,6 +1016,9 @@ settings.advanced.wrapper_launcher.prompt=如填写“optirun”后,启动命
|
||||
settings.custom=自定义
|
||||
|
||||
settings.game=游戏设置
|
||||
settings.game.copy_global=复制全局游戏设置
|
||||
settings.game.copy_global.copy_all=复制全部
|
||||
settings.game.copy_global.copy_all.confirm=你确定要覆盖当前版本特定游戏设置吗?此操作无法撤销!
|
||||
settings.game.current=游戏
|
||||
settings.game.dimension=游戏窗口分辨率
|
||||
settings.game.exploration=浏览
|
||||
@@ -1055,7 +1066,7 @@ settings.launcher.proxy.http=HTTP
|
||||
settings.launcher.proxy.none=不使用代理
|
||||
settings.launcher.proxy.password=密码
|
||||
settings.launcher.proxy.port=端口
|
||||
settings.launcher.proxy.socks=Socks
|
||||
settings.launcher.proxy.socks=SOCKS
|
||||
settings.launcher.proxy.username=账户
|
||||
settings.launcher.theme=主题
|
||||
settings.launcher.title_transparent=标题栏透明
|
||||
@@ -1072,7 +1083,6 @@ settings.memory.lower_bound=最低内存分配
|
||||
settings.memory.used_per_total=设备中已使用 %1$.1f GB / 设备总内存 %2$.1f GB
|
||||
settings.physical_memory=物理内存大小
|
||||
settings.show_log=查看日志
|
||||
settings.skin=现已支持离线账户更换皮肤,你可以到账户页面更改离线账户的皮肤和披风 (多人游戏下其他玩家无法看到你的皮肤)。
|
||||
settings.tabs.installers=自动安装
|
||||
settings.take_effect_after_restart=重启后生效
|
||||
settings.type=版本游戏设置类型
|
||||
@@ -1158,6 +1168,10 @@ version.manage.rename.fail=重命名版本失败,可能文件被占用或者
|
||||
version.settings=游戏设置
|
||||
version.update=更新整合包
|
||||
|
||||
wiki.tooltip=Minecraft Wiki 页面
|
||||
wiki.version.game.release=https://zh.minecraft.wiki/w/Java版%s?variant=zh-cn
|
||||
wiki.version.game.snapshot=https://zh.minecraft.wiki/w/%s?variant=zh-cn
|
||||
|
||||
wizard.prev=< 上一步
|
||||
wizard.failed=失败
|
||||
wizard.finish=完成
|
||||
|
||||
+5177
-1143
File diff suppressed because it is too large.
Load diff
@@ -1,9 +1,9 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
#
|
||||
# mcmod.cn
|
||||
# Copyright (C) 2024. All Rights Reserved.
|
||||
# Copyright (C) 2025. All Rights Reserved.
|
||||
#
|
||||
gt-new-horizons;1;;格雷科技:新视野;GT: New Horizons;GTNH
|
||||
enigtech-2;2;;玄理2;EnigTech 2;ET2
|
||||
@@ -39,7 +39,7 @@ modern-skyblock-3-departed;31;;现代空岛3;Modern Skyblock 3: Departed;
|
||||
gregblock;32;;格雷空岛;Gregblock;
|
||||
sevtech-ages;33;;赛文科技;SevTech: Ages;STA
|
||||
;34;;伊卡洛斯号迫降;Icarus Landing;IL
|
||||
greedycraft;35;;贪婪整合包;GreedyCraft;
|
||||
greedycraft;35;;贪婪整合包;GreedyCraft;GC
|
||||
stoneblock;36;;StoneBlock;;
|
||||
ftb-presents-skyfactory-3;37;;天空工厂3;FTB Presents SkyFactory 3;SF3
|
||||
space-astronomy;38;;Space Astronomy;;
|
||||
@@ -142,7 +142,7 @@ awakening-sky-of-diamonds;135;;Awakening - Sky of Diamonds;;
|
||||
ragnamod-v;136;;Ragnamod V;;
|
||||
all-the-mods-3-expert;137;;All the Mods 3 专家版;All the Mods 3 Expert;ATM3E
|
||||
seablock-rustic-waters;138;;乡村水域;Seablock: Rustic Waters;
|
||||
engineers-life;139;;Engineer's Life;;
|
||||
engineers-life;139;;Engineer's Life;;EL
|
||||
star-factory;140;;Star Factory;;
|
||||
lunar-laboratory;141;;Lunar Laboratory;;
|
||||
glacial-awakening;142;;Glacial Awakening;;
|
||||
@@ -170,7 +170,7 @@ all-the-mods-6;164;;All the Mods 6;;ATM6
|
||||
garden-of-glass-questbook-edition;165;;Garden of Glass (Questbook Edition);;
|
||||
;166;;圣剑传奇;Sword Legendary;
|
||||
;167;;FTB Presents Direwolf20 1.16;;
|
||||
team-rustic;168;;Team Rustic;;TR
|
||||
team-rustic;168;;Rusty Sink 1.20;;
|
||||
enigmatica2expertskyblock;169;;Enigmatica 2: Expert Skyblock;;E2ES
|
||||
;170;;休闲-生存-2021版;Leisure Survival-2021;ls21
|
||||
hexxit-updated;171;;Hexxit Updated;;
|
||||
@@ -217,7 +217,7 @@ airaka-tech;211;;艾瑞卡科技;Airaka Tech;
|
||||
originssmp;213;;起源SMP;OriginsSMP;
|
||||
create-live-2;214;;Create Live 2;;
|
||||
origin-smp-copy;215;;Origin - SMP;;
|
||||
;216;;逆迫降;INUERSE FORCED LANDING;
|
||||
;216;;逆迫降;INVERSE FORCED LANDING;
|
||||
avatar-the-four-elements;217;;Avatar: The Four Elements;;
|
||||
seaopolis;218;;Seaopolis;;
|
||||
zombie-apocalypse-remastered;219;;僵尸启示录:重制;Zombie Apocalypse Remastered;ZAR
|
||||
@@ -253,9 +253,9 @@ evolution-reset;250;;进化:重启;Evolution: Reset;ER
|
||||
the-nuclear-wasteland;251;;核荒原;The Nuclear Wasteland;
|
||||
blessed-or-cursed-expedition-to-infinite-force;252;;祝福或诅咒:无限力量之征;Blessed or Cursed: Expedition to Infinite Force;BOC
|
||||
;253;;边界生存;Border Survival;BSV
|
||||
medieval-mc-fabric;255;;Medieval Minecraft;;
|
||||
medieval-mc-fabric;255;;Medieval Minecraft;;MMC
|
||||
heavens-of-sorcery;256;;巫术天堂;Heavens of Sorcery;
|
||||
herodotus;257;;希罗多德;Herodotus;HDS
|
||||
herodotus;257;;希罗多德;Herodotus-Authentic Edition:Official;HDS
|
||||
;258;;重生世界;Rebirth world;RW
|
||||
permafrost-eots;259;;永久冻土:风暴之眼;Permafrost - Eyes of the Storm;
|
||||
ultimate-alchemy;260;;终极炼金;Ultimate Alchemy;
|
||||
@@ -269,7 +269,7 @@ the-kingdom-of-daldar-forge-labs;267;;The Kingdom of Daldar - Forge Labs;;
|
||||
demon-slayers-unleashed;268;;Demon Slayers Unleashed;;
|
||||
valhelsia-enhanced-vanilla;269;;Valhelsia: Enhanced Vanilla;;
|
||||
;270;;FTB University 1.16;;
|
||||
thaumic-renaissance-kedition;271;;神秘复兴;Thaumic Renaissance Kedition;
|
||||
thaumic-renaissance-kedition;271;;神秘复兴;Thaumic Renaissance / Thaumic Renaissance - Kedition;
|
||||
the-winter-rescue;273;;冬季救援;The Winter Rescue;TWR
|
||||
;274;;飞翔之路;Flying Road;
|
||||
forever-stranded-lost-souls;275;;Forever Stranded Lost Souls;;
|
||||
@@ -281,11 +281,11 @@ manufactio;281;;Manufactio;;
|
||||
world-of-dragons-ii;282;;World of Dragons II;;
|
||||
tensura-unleashed;283;;Tensura Unleashed;;
|
||||
skyopolis-4;284;;Skyopolis 4;;
|
||||
;286;;FTB OceanBlock;;
|
||||
ftb-oceanblock;286;;FTB OceanBlock;;
|
||||
tolkiencraft-iii-return-to-middle-earth;287;;TolkienCraft III - Return to Middle-earth;;
|
||||
delivery-inc;288;;Delivery Inc.;;
|
||||
poetica;289;;Poetica;;
|
||||
divine-journey-2;290;;Divine Journey 2;;DJ2
|
||||
divine-journey-2;290;;神圣之旅2;Divine Journey 2;DJ2
|
||||
all-the-mods-7;291;;All the Mods 7;;ATM7
|
||||
skyfactory-one;292;;SkyFactory One;;
|
||||
feed-the-singularity;293;;喂养奇点生存;Feed the Singularity;
|
||||
@@ -374,12 +374,11 @@ jurassic-world-reborn;376;;Jurassic World Reborn;;
|
||||
;377;;FTB Omnia;;
|
||||
fantazy-tech-expert-mode;378;;幻想科技;Fantazy Tech;
|
||||
crainer-craft-2;379;;Crainer Craft 2;;
|
||||
;380;;All The Forge 8;;
|
||||
all-the-forge-8;380;;All The Forge 8;;
|
||||
colony-space-modpack;381;;KN: Colony New Worlds;;
|
||||
skyopolis-3;382;;Skyopolis 3;;
|
||||
aboe-3;383;;A Bit of Everything 3;;
|
||||
aboe-3;383;;A Bit of Everything 3;;ABOE3
|
||||
sky-alchemist;384;;天空炼金术士;Sky Alchemist;
|
||||
epicadventure;385;;奥德赛;OdysseyCraft;OC
|
||||
;386;;字字落实;;
|
||||
nuclear-earth-reirradiated;387;;核地球:再辐照;Nuclear Earth: Reirradiated;
|
||||
god-block;388;;God Block;;
|
||||
@@ -392,7 +391,7 @@ gregtech-community-pack;393;;GregTech Community Pack;;GCP
|
||||
create-yourself;395;;Create Yourself;;
|
||||
starrynight-pray-stp;397;;星夜祈临;StarryNight Pray;StP
|
||||
rustic-waters-ii;398;;乡村水域2;Rustic Waters II;
|
||||
enigmatica6expert;399;;Enigmatica 6 Expert;;E6E
|
||||
enigmatica6expert;399;;Enigmatica 6: Expert;;E6E
|
||||
celestial-journey;400;;Celestial Journey;;
|
||||
;401;;天空舞台;Skyblock Create Show;
|
||||
project-architect;402;;Project Architect;;
|
||||
@@ -406,10 +405,10 @@ kimetsu-no-yaiba-demon-slayer-mod-pack;409;;鬼灭之刃;Kimetsu no Yaiba;
|
||||
;410;;七曜之际;FOR A WEEK;
|
||||
not-too-complicated;411;;Not Too Complicated;;NTC
|
||||
fiefdom;412;;列土封疆;Fiefdom;
|
||||
reinforce-reality;413;;死亡工艺&重生;Deathcraft & Rebirth;
|
||||
reinforce-reality;413;;死亡工艺&重生;Deathcraft & Rebirth / Reinforce Reality;
|
||||
afterlife;414;;The Afterlife;;
|
||||
create-skyblock;415;;CREATE Skyblock;;
|
||||
lightcraftproject;416;;Light Craft;;
|
||||
lightcraftprojectlite;416;;Light Craft;;
|
||||
epoch-runner-formerly-nuclear-beyond-modpack;417;;时代领跑人;Epoch Runner;
|
||||
;418;;星海征途;The Journey of Star Sea;TJoSS
|
||||
roguelike-adventures-and-dungeons-2;419;;冒险与地牢2;Roguelike Adventures and Dungeons 2;RAD2
|
||||
@@ -422,7 +421,7 @@ magnificia;423;;Magnificia;;
|
||||
chroma-sky-2;426;;色度空岛2;Chroma Sky 2;
|
||||
infinity-fabric;427;;无限;Infinity;IF
|
||||
enigmatica-2-expert-extended;428;;Enigmatica 2: Expert - Extended;;E2E-E
|
||||
;429;;水星迫降重制版;Mercury landing;
|
||||
mercury-landing;429;;水星迫降重制版;Mercury landing;
|
||||
magic-sky;430;;Magic Sky;;
|
||||
;431;;科技飞升;Rise of Tech;RoT
|
||||
not-too-complicated-2;432;;Not Too Complicated 2;;
|
||||
@@ -448,7 +447,7 @@ lcw;453;;失落文明:野原;Lost_Civilization:Wilds;LCW
|
||||
promo-cinematic;454;;Minecraft trailer recreation | Promo-Cinematic Modpack;;
|
||||
the-last-one;455;;最后一人;The Last One;TLO
|
||||
;456;;帕斯特之梦;PasterDream;PD
|
||||
;457;;失落的米德加尔特;Lost Midgard;LM
|
||||
lost-midgard;457;;失落的米德加尔特;Lost Midgard;LM
|
||||
;458;;真实空岛;Real Island;RI
|
||||
abyss-hunter;459;;深渊猎人;Abyss Hunter;AH
|
||||
blackstone-block;460;;Blackstone Block;;
|
||||
@@ -458,14 +457,14 @@ mcdoom-modpack;463;;MCDoom Modpack;;
|
||||
poko;464;;Poko;;
|
||||
;465;;永无止境:重缝;Cabricality;CABF
|
||||
aperomods;466;;Aperomods;;APM
|
||||
;467;;XPlus 2.0 基础整合;XPlus Modpack;X+
|
||||
;467;;XPlus 2.0 基础整合;XPlus 2.0 Modpack;X+
|
||||
crazy-crave-5;468;;Crazy Crave 5;;
|
||||
sky-research;469;;Sky Research;;
|
||||
;470;;魔力做功;Work Done by Mana;WDM
|
||||
;471;;Beyond Reality;;
|
||||
slcp4;472;;微笑草坪4;SmileLawn 4;SLCP4
|
||||
crazy-craft-updated;473;;Crazy Craft Updated;;
|
||||
planetary;474;;Planetary;;
|
||||
planetary;474;;行星;Planetary;
|
||||
slcp5;475;;微笑草坪5;SmileLawn 5;SLCP5
|
||||
;476;;寂静传说;tales of silent;TOS
|
||||
Wightcraft;477;;Wightcraft;;
|
||||
@@ -481,7 +480,7 @@ jetpack-cat;482;;喷气背包猫;Jetpack Cat;
|
||||
mineshafts-monsters-lite;487;;Mineshafts & Monsters - LITE;;
|
||||
;488;;AELife;;AEL
|
||||
mineshafts-monsters-adventure;489;;Mineshafts & Monsters - Adventure;;
|
||||
dimensionhopper;490;;DimensionHopper;;
|
||||
dimensionhopper;490;;Dimension Hopper:The Fall;;
|
||||
technology-empty-island;491;;科技空岛;RxanGrben;
|
||||
greed-the-resource-4-infinity;492;;Greed the Resource 4 - Infinity;;
|
||||
caveopolis;493;;Caveopolis;;
|
||||
@@ -500,8 +499,8 @@ other-body;504;;Other Body;;
|
||||
;506;;莱姆星云;Lime Nebula;LN
|
||||
void-genesis;507;;Void Genesis;;
|
||||
create-flavored;508;;Create Flavored;;
|
||||
terrafirmagreg;509;;TerraFirmaGreg;;TFG
|
||||
create-astral;510;;Create: Astral;;
|
||||
terrafirmagreg-vintage;509;;TerraFirmaGreg;;TFG
|
||||
create-astral;510;;机械动力:星辰;Create: Astral;
|
||||
;511;;起源创造;CreateOrigin;CO
|
||||
random-item-skyblock-recondite-i;512;;Random Item Island - Recondite I;;
|
||||
infinity-foundation;513;;Infinity Foundation;;
|
||||
@@ -517,7 +516,7 @@ tankoptimization;521;;坦坦优化;Tankoptimization;
|
||||
pick-the-stars;523;;摘星;Pick The Stars;PTS
|
||||
earth-technology;524;;Earth Technology;;
|
||||
;525;;核乐不为;;ZMC
|
||||
;526;;FTB StoneBlock 3;;
|
||||
ftb-stoneblock-3;526;;FTB StoneBlock 3;;
|
||||
create-mod-plus;527;;Create +;;
|
||||
multiplayer-optimization;528;;RawDiamondMC的优化包;RawDiamondMC's optimization pack;MPO
|
||||
chroma-endless;529;;Chroma Endless;;
|
||||
@@ -532,12 +531,12 @@ fos-community-edition;534;;奇点工厂1:社区版;Factory of Singularity:Comm
|
||||
;538;;跃迁计划;CT TransitionPlan;CTTP
|
||||
astropolis;539;;Astropolis;;
|
||||
;540;;筑梦者;Builder Dream;
|
||||
;541;;尘埃传说;dusttale;
|
||||
;541;;尘埃传说;;
|
||||
magiculture-2;542;;Magiculture 2;;
|
||||
;543;;交错维度2;Among Dimensions 2;AD
|
||||
hexteria-skies;544;;Hexteria Skies;;
|
||||
;545;;Omniworld;;
|
||||
a-l;546;;冒险生活;Adventure Life;
|
||||
al-legacy;546;;冒险生活-L;Adventure Life - Legacy;ALL
|
||||
skymachina;547;;SkyMachina;;
|
||||
all-of-fabric-6;548;;All of Fabric 6;;AOF6
|
||||
;549;;机械动力:锄与锤;;CHH
|
||||
@@ -546,11 +545,11 @@ technologicaljourney;552;;Technological Journey;;TJ
|
||||
;553;;末世工匠;End-Time Craftsmen;EdTC
|
||||
;554;;光芒消逝之日;;VL
|
||||
gregic-skies;555;;Gregic Skies;;
|
||||
;556;;勇者之章;Chapter of Yuusha;
|
||||
;556;;勇者之章;Chapter of Yuusha;CY
|
||||
;557;;齿轮颂歌:暮光;Gear Carol;
|
||||
pluma;558;;Pluma, a Journey to the Future!;;Pluma
|
||||
all-the-mods-7-to-the-sky;559;;All the Mods 7 - To the Sky;;ATM7S
|
||||
;560;;月见苍穹传说:大地演化;TskimiCanopyCraft:Terra Evolution;TSCC:TE
|
||||
;560;;月见苍穹传说:大地演化;TskimiCanopyCraft: Terra Evolution;TSCC:TE
|
||||
;561;;最后的战役:劫后余生;The Last Stand:Aftermath;LSA/TLS
|
||||
;562;;小猪配平;Peppa Pig's Burning Broom;2P2B
|
||||
zerblands-remastered;563;;Zerblands-Remastered;;
|
||||
@@ -565,7 +564,7 @@ provefrom;569;;忘却之刻;Provefrom;PvF
|
||||
darkrpg-forge;572;;DarkRPG Forge;;
|
||||
expert-of-magic-art-world;573;;魔艺世界;Expert of Magic Art World;EOMAW
|
||||
;574;;龙境;Dragoncraft;DGC
|
||||
nuclear-radiation-2;576;;核污染二;;NR2
|
||||
nuclear-radiation-2;576;;核污染二;nuclear radiation 2;NR2
|
||||
;577;;白狐铃的奇思妙想;Arctic Fox Ling Fantasy;AFLF
|
||||
;578;;MineSpace;;MS
|
||||
;579;;史诗战斗:兵团战争;EPIC Fight Corps War;EFC
|
||||
@@ -579,22 +578,22 @@ multiblock-madness-2;583;;MultiBlock Madness 2;;MBM2
|
||||
e2eu;587;;Enigmatica 2 Expert Unofficial;;E2Eu
|
||||
e2eus;588;;Enigmatica 2 Expert Unofficial Skybound;;E2EuS
|
||||
;589;;龙之冒险;;
|
||||
;590;;格雷:量子跃迁;GregTech:QuantumTransition;GTQT
|
||||
gregtech-quantum-transition;590;;格雷:量子跃迁;GregTech:QuantumTransition;GTQT
|
||||
the-winter-frontier;591;;冬境边域;The Winter Frontier;TWF
|
||||
;592;;林深不知处;NO, is killed by... Twlight Forest?;NON
|
||||
;593;;太阳机械厂;Create Sun;CS
|
||||
dawn-craft;594;;破晓之界;DawnCraft;
|
||||
dawn-craft;594;;破晓之界;DawnCraft: Echoes of Legends;DC
|
||||
aof-presents-skylore;595;;AOF: Skylore;;
|
||||
;596;;可乐兔的格雷整合包;GtMagic;
|
||||
;597;;FTB Skies;;
|
||||
keletus-gregpack;596;;可乐兔的格雷整合包;Keletu's GregPack;
|
||||
ftb-skies;597;;FTB Skies;;
|
||||
tnp-limitless-5;598;;TNP Limitless 5;;
|
||||
create-back-on-track;599;;Create: Back on Track;;
|
||||
;600;;重构:炼金术;Refactoring: Alchemy;RA
|
||||
;601;;地心;Geocentric;GOC
|
||||
;602;;巫师·古老恶意;;
|
||||
;602;;巫师·古老恶意;Wizard against wicked;WaW
|
||||
;603;;进化太阳生存;Evolutionary Sun Survival;ESS
|
||||
changfeng;604;;长峰漫路;Long peak Long road;LPR
|
||||
techopolis-2;605;;Techopolis 2;;
|
||||
techopolis-2;605;;科技城2;Techopolis 2;
|
||||
;606;;Arcomua 原版整合;Arcomua Modpack;ARC
|
||||
;607;;UnityCraft;;UC
|
||||
;608;;灵犀;;
|
||||
@@ -606,7 +605,7 @@ create-beyond-earth;612;;Create: Beyond Earth;;
|
||||
;614;;Additive;;
|
||||
;615;;无人荒漠-新世界;;
|
||||
new-game-modpack-release;616;;New Game - An Improved Vanilla Experience;;
|
||||
prodigium-reforged;617;;Prodigium Reforged;;
|
||||
prodigium-reforged;617;;Prodigium Reforged (Terraria Pack);;
|
||||
;618;;FTB Presents Integration by Parts DX;;
|
||||
striving-standoff;619;;抗争之际;Striving Standoff;SS
|
||||
gun-fire-survival;620;;枪火生存;Gun fire survival;
|
||||
@@ -628,10 +627,10 @@ cobblemon-forge;635;;Cobblemon Official Modpack;;
|
||||
sbeevs-industrial-revolution;636;;Sbeev's Industrial Revolution;;
|
||||
;637;;Gensokyo Reimagined QOL;;
|
||||
statech-industry;638;;StaTech Industry;;
|
||||
vault-hunters-1-18-2;639;;Vault Hunters 3rd Edition;;
|
||||
vault-hunters-1-18-2;639;;宝藏猎人3;Vault Hunters 3rd Edition;
|
||||
survival-hard-rock;640;;生存硬摇滚;Survival Hard Rock;
|
||||
aged;641;;Aged;;
|
||||
;642;;The Lost Era;;
|
||||
the-lost-era-modpack;642;;The Lost Era;;
|
||||
vault-hunters-official-modpack;643;;Vault Hunters 2nd Edition;;
|
||||
;644;;Sodium Plus;;
|
||||
;645;;霜叶表达法;Wither Expression;WE
|
||||
@@ -640,7 +639,7 @@ create-live_4;646;;Create Live 4;;
|
||||
valhelsia-5;648;;Valhelsia 5;;
|
||||
caves-cliffs-plus;649;;洞穴与山崖:增强版;Caves & Cliffs: Plus;CC+
|
||||
enigmatica9expert;650;;Enigmatica 9: Expert;;E9E
|
||||
;651;;虚无之主;The Lord of Ascension;TLoA
|
||||
the-lord-of-ascension;651;;虚无之主;The Lord of Ascension;TLoA
|
||||
;652;;寄生科技;;PTY
|
||||
rise-of-tech-2;653;;科技飞升2;Rise of Tech 2;RoT2
|
||||
;654;;虚空居者;Void Dweller;
|
||||
@@ -653,7 +652,7 @@ valhelsia-6;660;;Valhelsia 6;;
|
||||
all-the-mods-volcanoblock;661;;All the Mods: Volcano Block;;ATMVB
|
||||
gregtronix-revoluton;662;;Gregtronix Revolution;;GR
|
||||
;663;;空岛物语;Empty Island RPG;EIRPG
|
||||
prominence-fabric;664;;Prominence I [FABRIC];;
|
||||
prominence-fabric;664;;卓越1 [FABRIC];Prominence I;
|
||||
multiskyblock-2;665;;模块化空岛2;MultiSkyBlock 2;MSB2
|
||||
;666;;The Minehattan Project;;
|
||||
;667;;万象天工;Millions of Heavens industries;MOHI
|
||||
@@ -677,7 +676,7 @@ musketeer;683;;Musketeer - One for All;;
|
||||
;685;;我的骑士:怪物大乱斗;;
|
||||
;686;;塔可努的原生优化整合包;TechNoob's Vanilla Boost Pack;TNVBP
|
||||
;687;;灵茗屿空岛;LMY SKY;
|
||||
;688;;绿色版红石生电优化;;
|
||||
;688;;红石生电优化;Redstone Survival Optimization;RSO
|
||||
;689;;恐惧深渊;Abyss of Fear;AoF
|
||||
;690;;德雷戈拉旅人日记;Diary of Dregora's Traveller;DoDT
|
||||
nwtcraft;691;;近西之旅;NwtCraft;Nwtc
|
||||
@@ -685,26 +684,26 @@ nwtcraft;691;;近西之旅;NwtCraft;Nwtc
|
||||
aeropolis;693;;Aeropolis;;
|
||||
project-sacrifice;694;;Project Sacrifice;;
|
||||
chosens-modded-adventure;695;;Chosen's Modded Adventure;;
|
||||
ciscos-fantasy-medieval-adventure-rpg;696;;Cisco's Fantasy Medieval Adventure RPG;;
|
||||
ciscos-fantasy-medieval-adventure-rpg;696;;Cisco's Fantasy Medieval RPG [Lite];;
|
||||
;697;;命运之轮;Wheel of Fate;
|
||||
all-the-mods-gravitas2;698;;All The Mods - Gravitas²;;ATMG2
|
||||
enigmatica9;699;;Enigmatica 9;;E9
|
||||
;700;;死亡突围;Deadout;DT
|
||||
create-arcane-engineering;701;;机械动力:奥术工程;Create: Arcane Engineering;CAE
|
||||
chroma-endless-2;702;;Chroma Endless 2;;
|
||||
techrevolution;703;;TechRevolution;;TECHR
|
||||
techrevolution;703;;TechRevolution;;
|
||||
cabin;704;;CABIN: Create Above & Beyond In Newer;;CABIN
|
||||
aoc;705;;All of Create;;Aofc
|
||||
aoc;705;;All of Create;;
|
||||
;706;;交错维度3;Among Dimensions 3;AD
|
||||
gregfactory-sky;707;;GregFactory Sky;;GFS
|
||||
;708;;飞翔之路4;Flying Road 4;
|
||||
;709;;星月传说;Celestial Legend;CL
|
||||
ctc-create-the-creation;710;;Create:the Creation;;CTC
|
||||
create-perfect-world;711;;Create: Perfect World;;
|
||||
new-simple-mods;712;;New Simple Mods;;
|
||||
;713;;灾变录;Apocalypse of Catastrophe;ACC
|
||||
new-simple-mods;712;;New Simple Mods - Easy to Understand;;
|
||||
;713;;灾变录;Catastrophe Record;CR
|
||||
sky-greg;714;;Sky Greg;;
|
||||
deceasedcraft;715;;DeceasedCraft;;
|
||||
deceasedcraft;715;;亡者世界;Deceased Craft;
|
||||
;716;;生欲:求生;Vivere The Zombie Apocalypse;VZA
|
||||
create-planetary;717;;Create Planetary;;
|
||||
master-mabaoguo;718;;Master mabaoguo;;
|
||||
@@ -717,41 +716,322 @@ the-backrooms-survival;724;;The Backrooms Survival;;
|
||||
;725;;13年前的梦;Dreams 13 years ago;
|
||||
cookiecrafter;726;;CookieCrafter;;
|
||||
;727;;理想国:科克肖特;Utopia:Cockshott;UCST
|
||||
bigchadguys-plus;728;;BigChadGuys Plus;;BCG+
|
||||
bcg;728;;BigChadGuys Plus;;BCG+
|
||||
;729;;异世界探险;ISEKAIBOUKEN;YSB
|
||||
create-explore-c-e;730;;Create & Explore;;C&E
|
||||
twistercraft;731;;TwisterCraft;;
|
||||
all-the-horror;732;;恐怖生存;All The Horror;
|
||||
the-backrooms-liminal-spaces;733;;The Backrooms;;
|
||||
;734;;伊甸空天;Eden Aerospace;EAS
|
||||
;735;;亚特兰深渊;AtlanAbyss;
|
||||
atlanabyss;735;;亚特兰深渊;AtlanAbyss;
|
||||
;736;;龙境II;Dragon Odyssey;DO
|
||||
;737;;龙之冒险:新征程;;
|
||||
all-the-mods-9-no-frills;738;;All the Mods 9 - No Frills;;ATM9NF
|
||||
toms-cobblemon;739;;Tom's Cobblemon;;
|
||||
life-in-the-village-3;740;;Life in the village 3;;LitV3
|
||||
;741;;联缘;NEXUS;
|
||||
;741;;联缘:NEXUS;;
|
||||
;742;;Create: Cinematic;;
|
||||
;743;;神秘纪元;Thaumic Epoch;ThE
|
||||
;745;;FTB Genesis;;
|
||||
cook-in-skyblock;746;;天空厨房;Cook in skyland;CiS
|
||||
;747;;被支配的恐惧;Dominated by fear;DF
|
||||
;748;;新的原版!;New Vanilla;NVA
|
||||
;747;;被恐惧支配 1;Dominated by Fear 1;DoF
|
||||
new-vanilla-modpack;748;;新的原版;New Vanilla;NVA
|
||||
;749;;群峦救援非官方版;Terrafirma Rescue Unofficial;TFRU
|
||||
stoneopolis;750;;Stoneopolis;;
|
||||
;751;;FTB Skies Expert;;
|
||||
ftb-skies-expert;751;;FTB Skies Expert;;
|
||||
wmshd-pack-what-mojang-should-have-done;752;;What Mojang Should Have Done;;WMSHD
|
||||
;753;;无限:重生;Infinity-Reborn;IF2
|
||||
;754;;随心之旅;spontaneous journey;
|
||||
oxmodpack;755;;OxMODPACK 8 +Online;;
|
||||
gregtech-community-pack-modern;756;;GregTech Community Pack Modern;;GCPM
|
||||
;757;;锻造大师2;Master Blacksmith2;MB2
|
||||
;757;;锻造大师3;Master Blacksmith3;MB3
|
||||
;758;;科技用于探索世界;Technology Exploration World;
|
||||
;759;;幻梦闲笔;Aetherial Voyage;AV
|
||||
;759;;幻梦闲笔;Aetherial Voyage;
|
||||
fear-the-mist;760;;雾中的恐惧;Fear The Mist;
|
||||
tekkit-resurrection;761;;Tekkit The Resurrection;;
|
||||
;762;;Cup Pineapple UBLU;;CPU
|
||||
;763;;FTB Arcanum Institute;;
|
||||
ftb-arcanum-institute;763;;FTB Arcanum Institute;;
|
||||
create-haven-adventures;764;;Create: Haven Adventures;;
|
||||
modecube;765;;ModéCube;;
|
||||
electrictechnocraft;766;;ElectricTechnoCraft;;
|
||||
create-2-mekanism;767;;Create 2 Mekanism;;C2M
|
||||
randblock;768;;随机空岛;RandBlock;RD
|
||||
;769;;格雷科技休闲版;GregTech Leisure;GTL
|
||||
frozenopolis;770;;Frozenopolis;;
|
||||
infernal-rpg;771;;Infernal Origins;;
|
||||
;772;;天狼星;Sirius;
|
||||
;773;;悠沙海;Ethereal Sand Sea;ESS
|
||||
;774;;AIR for JAVA;;AFJ
|
||||
;775;;Create Extra;;
|
||||
frs-optimisation-for-survival;776;;Fr的生存优化;Fr's Optimisation for Survival;FROS
|
||||
;777;;Exile Magic Constructor;;EMC
|
||||
;778;;Landscapes Reimagined Genesis;;
|
||||
al-nl;779;;冒险生活 - 新生活;Adventure Life - New Life;AL-NL
|
||||
harvista;780;;拾穗;Harvista;
|
||||
;781;;时间之书 : 星之旅行;BookofTime StarTravel;BKSL
|
||||
create-applied-energevation;783;;机械动力:应用能源时代;Create: Applied Energevation;CAEV
|
||||
;784;;新星工程:世界;Nova Engineering - World;NEW
|
||||
create-chronicles-bosses-and-beyond;785;;Create Chronicles: Bosses and Beyond;;
|
||||
;786;;Adrenaline;;
|
||||
forget-me-not;787;;勿忘我;Forget Me Not;FMN
|
||||
modpack-essentials;788;;整合优选;Modpack Essentials;ME
|
||||
desertopolis;789;;Desertopolis;;
|
||||
omniopolis;790;;Omniopolis;;
|
||||
;791;;战斗的法则;The Law of Battle;
|
||||
factory-of-singularity-ii-colorfulness;792;;奇点工厂II:绚彩;Factory of Singularity II: Colorfulness;FoS2
|
||||
prey-beta;793;;Prey;;
|
||||
;794;;魔法之旅;Magic journey;MJ
|
||||
;795;;轻松冒险;Easy adventure;EAT
|
||||
;796;;魔幻大陆2;Enchanted Lands 2;
|
||||
;797;;艾利克斯的世界;Alexcraft;AC
|
||||
terrafirmacraft-wiph-walden-lagu;798;;瓦尔登湖畔;Wiph Walden Lagu;WWL
|
||||
aocfabric;799;;All of Create Fabric;;
|
||||
craftingcraft;800;;合成工艺;CraftingCraft;
|
||||
;801;;感染者手册;Handbook for Infected People;HIP
|
||||
;802;;居无定所—中世旷野;;
|
||||
;803;;宾果难题;Bingo Madness;
|
||||
chemillas-reborn;804;;化学:重生;Chemilla's Reborn;CR
|
||||
;805;;群峦传说·异世界行纪;Terra Firma Craft:Travel in Another World;TFC:TAW
|
||||
milkyway;806;;Create: Milkyway;;
|
||||
farming-crossing-4;807;;Farming Crossing 4;;
|
||||
hardrock-terrafirmacraft-4-realistic-extreme;808;;HardRock TerraFirmaCraft;;TFCH/HTFC
|
||||
terrafirma-rebirth;809;;群峦:重生;TerraFirma: Rebirth;TFCR
|
||||
create-live-5-skyblock;810;;Create Live 5;;
|
||||
;811;;万象征程;Cosmos of Endless Allegory;CEA
|
||||
;812;;远古之遗;;
|
||||
;813;;奈の奇妙冒险;Kotona's Mystery Adventure;KSMA
|
||||
;814;;战争魔匠;war craftman;WC
|
||||
;815;;命定IV;unique destiny IV;
|
||||
;816;;Wither Storm Enhanced;;
|
||||
;817;;朝花夕拾;Dawn Blossoms Plucked at Dusk;
|
||||
la;818;;胶兽大冒险;Latex Advanture;LA
|
||||
mystics-monstrosity;819;;Mystic's Monstrosity;;
|
||||
;820;;更完美的MC;More Perfect Minecraft;MPMC
|
||||
;821;;动画世界:烂柯人;AnimateCraft: Lankeren;
|
||||
;822;;New Create;;NC
|
||||
land-of-memories;823;;回忆之地;;LOM
|
||||
;824;;异世界冒险2;;
|
||||
ragnamod-vii;825;;Ragnamod VII;;
|
||||
path-of-truth;826;;真理之路;Path of Truth;PoT
|
||||
;827;;TFC生活质量更新;;
|
||||
all-the-mods-9-to-the-sky;828;;All The Mod 9 - To The Sky;;ATM9S
|
||||
;829;;魔金:探秘;ManaMetalMod Exploration;M3E
|
||||
fallenpetals;830;;落英;FallenPetals;FP
|
||||
dungeon-heroes;831;;Dungeon Heroes (RPG Series);;
|
||||
play-as-dragon-gothic-edition;832;;Play as Dragon: Gothic Edition;;
|
||||
skyblock-burgeria;833;;空岛汉堡店;;
|
||||
crafting-craft-2;834;;合成工艺2;Crafting Craft 2;
|
||||
;835;;僵尸入侵100天;Zombie Invade 100 Days;ZI100D
|
||||
compact-world-expert;836;;Compact World Expert;;CWE
|
||||
a-fragment-of-peace;837;;宁然一隅;A Fragment of Peace;AFoP
|
||||
easy-create-modpack;838;;Easy Create;;
|
||||
;839;;流线鱼概念;Fish Spi Rally;FSRMP
|
||||
meatballcraft;840;;肉丸工艺,次元提升;MeatballCraft, Dimensional Ascension;
|
||||
seaopolis-submerged;841;;海之城: 潜没水中;Seaopolis: Submerged;
|
||||
;842;;空中厕所;PoopSky;
|
||||
compact-sky;843;;收缩空岛;Compact Sky;
|
||||
back-to-back;844;;背靠背;Back to Back;BTB
|
||||
adventure-exploration-part2;845;;冒险与探索;Adventure & Exploration;AnE
|
||||
;846;;复兴;Renaissance;
|
||||
live-like-a-castaway;847;;木筏求生2;Raftcraft2;RC2
|
||||
mermaid-legend;848;;人鱼传说;Mermaid legend;
|
||||
;849;;文明时代;;Aoc
|
||||
create-lost-and-renaissance;850;;机械动力:失落与复兴;Create Lost and Renaissance;CLR
|
||||
all-the-mods-10;851;;All the Mods 10;;ATM10
|
||||
;852;;格雷伊甸:建造;GregEden:Building;GEB
|
||||
;853;;迷失时代;LOST ERA;LE
|
||||
create-alchemy-plan;854;;机械动力:炼金计划;Create:Alchemy Plan;CAP
|
||||
deus-ex-machina;855;;Deus Ex Machina;;
|
||||
create-prepare-to-dye;856;;Create Prepare to Dye;;
|
||||
;857;;All The Fabric 5;;
|
||||
create-leisurely-delight;858;;机械动力:悠然乐事;Create Delight;CD
|
||||
;859;;蔚蓝悠悠;Azure Leisurely;AL
|
||||
infinityevolved-reloaded;860;;Infinity Evolved: Reloaded;;
|
||||
;861;;空中厕所:生产线;Poopsky:Assembly Line;PAL
|
||||
;862;;佛罗伦萨迷思;Fantasy in Florence;FIF
|
||||
craftoria;863;;Craftoria;;
|
||||
mechanic-craft;864;;MechanicCraft;;
|
||||
;865;;无用之人-群星;Good For Nothing:Stellaris;GFN
|
||||
;866;;打金无限;;
|
||||
ciscos-adventure-rpg-ultimate;867;;Cisco's Fantasy Medieval RPG [Ultimate];;
|
||||
jour-avancee-daylight;868;;Jour avancée/DayLight;;DL
|
||||
;869;;勇者之章 Ⅱ;Chapter of Yuusha II;CY2
|
||||
;870;;Beta 1.7.4;;B1.7.4
|
||||
;871;;神不在的星期天;God Without Create;GWC
|
||||
createa-colony;872;;Create'a Colony;;
|
||||
;873;;黑暗深处;Deep Dark;
|
||||
;874;;自在极境;Free Extreme Realm;
|
||||
all-of-fabric-7;875;;All of Fabric 7;;AOF7
|
||||
ftb-neotech;876;;FTB NeoTech;;
|
||||
;877;;Minecraft+;Minecraft plus;MCP
|
||||
;878;;龙珠Super;Dragon Ball Super;
|
||||
create-vanilla-exploration;879;;Vanilla Create Exploration;;
|
||||
progress;880;;Progress: Create;;
|
||||
aeon-automata;881;;Aeon Automata;;AA
|
||||
;882;;被恐惧支配2:收容失效;Dominated by Fear 2 Containment Breach;DoF2
|
||||
;883;;剑拔弩张之时;;TC
|
||||
project-architect-2;884;;Project Architect 2;;
|
||||
;885;;路网通途 / 更好的我的世界铁路;Railway Network Access;RNA
|
||||
create-mekanized;886;;Create: Mekanized;;
|
||||
create-perfect-world-2;887;;Create: Perfect World 2;;
|
||||
create-stellar;888;;Create Stellar;;
|
||||
monifactory;889;;Monifactory;;
|
||||
create-factory-builder;890;;Create: Factory Builder;;
|
||||
;891;;群峦传说:宝宝快乐版;TerraFirma: Baby Happy Edition;TFBH
|
||||
;892;;阶段: 残骸;Phase: the Debris;
|
||||
;893;;AE管道;AE Pipes;APE
|
||||
skyt-2;894;;SkyT 2;;
|
||||
stagecraft;895;;StageCraft;;
|
||||
;896;;我的世界从来不简单;Minecraft Is Never Easy;MINE
|
||||
ctnh;897;;机械动力:新视野;Create: New Horizon;CTNH
|
||||
agora-interregnum;898;;Agora Interregnum;;
|
||||
evolving-technology;899;;Evolving Technology;;
|
||||
enlightened-6-expert;900;;Enlightened 6 Expert;;En6E
|
||||
;901;;历久尤新;Old But New;OBN
|
||||
;902;;继承;Inherit;IN
|
||||
lucky-world-invasion;903;;幸运世界入侵;Lucky World Invasion;LWI
|
||||
ftb-interactions-remastered;904;;FTB Interactions Remastered;;FTBIR
|
||||
;905;;崩坏-新生;Broken-New Life;BNL
|
||||
;906;;持之永恒重生;Stay With Eternity Rebirth;SWER
|
||||
shadow-awakening;907;;暗影觉醒;Shadow Awakening;SAG
|
||||
;908;;理想箱庭物语;Second Eden;SE
|
||||
finality-genesis-1-20-1;909;;Finality Genesis;;
|
||||
;910;;死亡突围 · 重临死境;DeadoutRe;DTR
|
||||
soa3;911;;Slow Start AoA3;;SoA3
|
||||
new-vanilla-2;912;;新的原版 2;New Vanilla 2;NVA2
|
||||
prominence-2-rpg;913;;卓越 II RPG;Prominence II RPG;
|
||||
;914;;伟大冒险者:世界之眼;Great Adventurer:the Eye of the World;GAEW
|
||||
;915;;破碎;Fractures Rainimator;Rain
|
||||
;916;;更好的原版;Better Vanilla;BV
|
||||
strictly-medieval-3-forge;917;;Strictly Medival 3;;
|
||||
;918;;潘神的迷宫;Pan's Labyrinth;PL
|
||||
;919;;持之永恒-天空挑战者;Stay With Eternity Sky Challenger;SWESC
|
||||
easy-to-ascension;920;;易如飞升;Easy to Ascension;
|
||||
;921;;幽匿末日;The Last Day of Sculk;TLDS
|
||||
;922;;交错空岛;Crossisland;CrI
|
||||
;923;;机械动力:魔法乐事;Create Magic's Delight;CRMD
|
||||
minetech-evolution;924;;科技进化;Minetech Evolution;MTE
|
||||
gregified-sky-edition;925;;Gregified: Sky Edition;;
|
||||
;926;;原版扩展;Additonal Vanilla;AV
|
||||
create-on-a-potato;927;;Create on a Potato PC: Ultimate;;CPU
|
||||
;928;;现实主义;realism;RLM
|
||||
;929;;晋升之路;path of advance;POA
|
||||
;930;;永无止境:超越常新;Above and Beyond : Refresh;CABR
|
||||
cosmic-frontiers;931;;Cosmic Frontiers;;
|
||||
integrated-minecraft;932;;Integrated MC;;
|
||||
;933;;Rising Legends;;
|
||||
;934;;城乡之旅;Travel Township;TT
|
||||
;935;;龙鸣牢大;FarmerKobeBryant;FKB
|
||||
setgamedifficulty-reika;936;;SetGameDifficulty Reika;;
|
||||
epic-journey-gtceu;937;;Epic Journey GTCEu;;
|
||||
;938;;飞渡群峦;TerraFirmaCraft:Leap The Top;TFC:LTT
|
||||
gregtech-skybound;939;;Gregtech: Skybound;;
|
||||
simple-tfc;940;;简单群峦;SimpleTFC;
|
||||
ftb-presents-direwolf20-s14;941;;FTB Presents Direwolf20 1.21;;
|
||||
final-judgment-save-the-world;942;;Final Judgment:Save the world;;
|
||||
;943;;极端世界:重置;Extreme World Reset;EWR
|
||||
explore-everything;944;;空岛:探索;Explore Everything;SEE
|
||||
epic-journey-gtceu-conquerors-edition;945;;Epic Journey GTCEu - Conqueror's Edition;;
|
||||
the-undead-blooms;946;;亡灵绽放 | 真菌孢子启示录 |;The Undead Blooms | Fungal Spore Apocalypse |;
|
||||
;947;;星地征程;Starland Journey;SJ
|
||||
;948;;希望与绝望-惊变100天;Hope and Despair;had
|
||||
;949;;梦篡光阴;Dreams Distort Overture;DDO
|
||||
;950;;最后的光芒;The Last Light;TLL
|
||||
endil-expert-edition;951;;Endil's Expert Edition;;EEE
|
||||
peace-of-mind;952;;Peace of Mind;;
|
||||
distantly-optimized;953;;Distantly Optimized;;DO
|
||||
farming-experience;954;;Farming Experience;;
|
||||
hungry-for-tech-2;955;;Hungry For Tech 2;;
|
||||
remain;957;;Remain;;
|
||||
;958;;末日狂徒;;
|
||||
gregtania6-skyblock;959;;Gregtania6 Skyblock;;
|
||||
harmonious-engineering;960;;Harmonious Engineering;;
|
||||
valley-of-farming-and-ranching;961;;农牧物语;Valley of Farming and Ranching;
|
||||
;962;;机械动力:火车;Create Train;
|
||||
;963;;理想国冒险之旅;;AOIC
|
||||
;964;;生欲:求生2;Vivere The Zombie Apocalypse-II;VZA2
|
||||
;965;;起源-世界;OriginCraft Pulses;
|
||||
;966;;Legacies;;
|
||||
the-legend-of-2900;967;;海上机械师;The Legend Of 2900;TLOU
|
||||
mirasites-100-days;968;;畸变:蛊之奇境;Mirasites-BrokenMemories;MBM
|
||||
;969;;末日余晖:独自一人;;MRYH
|
||||
;970;;幸存者与僵尸;Survivors And Zombies;SAZ
|
||||
;971;;海岛寿司店;Sushi Island;
|
||||
infinite-alchemy;972;;Infinite Alchemy;;IA
|
||||
;973;;探索的时光;Adventuring Time;AT
|
||||
;974;;饕餮飨食;;
|
||||
;975;;丰富的MC;RichMC;RM
|
||||
;976;;刀剑异闻录;Sword Strange Tales;SST
|
||||
etbw;977;;探索光明的世界;Explore the Bright World;EtBW
|
||||
hardship-era;978;;苦难年代;Hardship Era;HE
|
||||
rlcraft-dregora;979;;RLCraft Dregora;;
|
||||
;980;;Farm The Ingots;;FTI
|
||||
venoms-craft;981;;Venom's Craft;;
|
||||
;982;;慢生活冒险;;
|
||||
;983;;多元工艺:绘镇曲;Painted Town Melody;PTM
|
||||
;984;;遗忘之海;Leisurely Life;
|
||||
;985;;幻想国度;F_Land;
|
||||
;986;;群峦传说:重制;TerraFirmaCraft Remake;TFCRe
|
||||
;987;;未来;;
|
||||
;988;;起源生物—崛起;;
|
||||
;989;;工匠:技艺革新;Construct:Technological Innovation;CTI
|
||||
erstwhile-journey;990;;往昔之旅;Erstwhile Journey;EJ
|
||||
;991;;Exploria;;
|
||||
ftb-evolution;992;;FTB Evolution;;
|
||||
;994;;云程发轫;cloud journey begins;CJB
|
||||
forever-factory;995;;永恒工厂;Forever Factory;
|
||||
;996;;FTB University 1.19;;
|
||||
symbolica;997;;Symbolica;;
|
||||
aelstar;998;;Aelstar;;
|
||||
srp-qz;999;;SRP:隔离区;SRP: Quarantine Zone;
|
||||
industrial-machines;1000;;Industrial Machines;;
|
||||
auto-terrafirmacraft-tfc-create;1001;;Auto-TerraFirmaCraft (TFC + Create);;ATFC
|
||||
;1002;;时间之书:星的日记;The Book of Time: A Star's Journal;TASL
|
||||
;1003;;特摄世界;Tokusatsu World;TW
|
||||
journey-of-reincarnation;1004;;转世之旅;Journey of Reincarnation;
|
||||
;1005;;冒险与休闲;Adventure and Leisure;AL
|
||||
enigmatica10;1006;;Enigmatica 10;;E10
|
||||
mc-chocolate-edition;1007;;The Chocolate Edition;;
|
||||
civilization-l;1008;;Civilization-L;;
|
||||
;1009;;群峦:降临;TerraFirma:Advent;TFA
|
||||
valhelsia-7;1010;;Valhelsia 7;;
|
||||
mechanical-mastery-plus;1011;;Mechanical Mastery Plus;;
|
||||
ashes-to-ashes;1012;;尘归尘;Ashes To Ashes;ATA
|
||||
;1013;;格雷乐事;GT So Easy;GTse
|
||||
create-silence-and-prosperity;1014;;机械动力:寂静与繁荣;Create Silence and Prosperity;CSP
|
||||
mif;1015;;MI:Foundation;;MIF
|
||||
c2mse2;1016;;Create 2 Mekanism: Sky Edition 2;;C2MSE2
|
||||
;1017;;Tsuri No Gatsu 5 - FDCraft Version;;TSNG5-FDC
|
||||
;1018;;无限战争4;Infinity War4;INF4
|
||||
;1019;;群峦传说:工业长路;;
|
||||
;1020;;自然冒险 初生;Nature Adventure Newborn;NAB
|
||||
;1021;;羽舲的基础优化;Hanekmio's Basis Optimization;
|
||||
create-star-valley;1022;;Create: Gears of History;;
|
||||
;1023;;阿尔法计划;AlphaPlan;AP
|
||||
spell-dimension;1024;;咒次元;Spell Dimension;SD
|
||||
skyfactory-5;1025;;天空工厂 5;SkyFactory 5;SF5
|
||||
;1026;;破劫之刃;Break Disaster;
|
||||
reminiscent-create;1027;;Reminiscent Create;;
|
||||
techsert;1028;;Techsert;;TH
|
||||
prehistoric-world-dinosaurs-adventure;1029;;Prehistoric World - Dinosaurs & Adventure;;
|
||||
terminal;1030;;终末;Terminal;
|
||||
;1031;;异次元水晶 3;Isolated Crystal 3;
|
||||
;1032;;机械动力:无限总是小于零;Create:Infinity Always Smaller Than Zero;CIASZ
|
||||
;1033;;踪灭I-开端;;
|
||||
;1034;;踪灭II;;
|
||||
rabbits-dawn-craft;1035;;某兔子的破晓之界;A Certain Rabbit's DawnCraft - Dawn of the Deep Soul;RDC
|
||||
;1036;;铁砧天空;;ASKY
|
||||
grand-architect-journey;1037;;伟大工程巡礼;Grand Architecture Journey;GAJ
|
||||
the-adventure-of-valkyriens;1038;;瓦尔基里大冒险;The Adventure of Valkyriens;TAOV
|
||||
;1039;;方块宝可梦:奥瑞星光;Cobblemon Ori Starlight;COS
|
||||
;1040;;群峦传说:天才版;;
|
||||
compression;1041;;Compression;;
|
||||
compact-sky-easy;1042;;Compact Sky: Easy;;CSE
|
||||
;1043;;丰收与探索;;
|
||||
;1044;;脆骨症:黯光;No Flesh Within Chest-DIM;NFWC-DIM
|
||||
;1045;;小雷的优化冒险;;
|
||||
cobblemon-eternal-edition;1046;;Cobblemon Eternal;;
|
||||
;1047;;Tinkerer's Quilt;;
|
||||
gregtech-expert;1048;;GregTech Expert;;
|
||||
;1049;;Beta Horizons;;
|
||||
minecraft-legendary-edition;1050;;Legendary Edition;;
|
||||
@@ -3986,38 +3986,27 @@
|
||||
"com.mojang:text2speech:1.13.9:natives-linux": null
|
||||
},
|
||||
"windows-x86_64": {
|
||||
"software-renderer-loader": {
|
||||
"name": "org.glavo:llvmpipe-loader:1.0",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/glavo/llvmpipe-loader/1.0/llvmpipe-loader-1.0.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/glavo/llvmpipe-loader/1.0/llvmpipe-loader-1.0.jar",
|
||||
"sha1": "ff255415e5c4b2a18970da0a8e552b557ca013ae",
|
||||
"size": 12964773
|
||||
}
|
||||
}
|
||||
},
|
||||
"mesa-loader": {
|
||||
"name": "org.glavo:mesa-loader-windows:0.2.0:x64",
|
||||
"name": "org.glavo:mesa-loader-windows:0.3.0:x64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/glavo/mesa-loader-windows/0.2.0/mesa-loader-windows-0.2.0-x64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/0.2.0/mesa-loader-windows-0.2.0-x64.jar",
|
||||
"sha1": "adb4a16ecb95c8944a016829b05d70f934ea1a29",
|
||||
"size": 22823052
|
||||
"path": "org/glavo/mesa-loader-windows/0.3.0/mesa-loader-windows-0.3.0-x64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/0.3.0/mesa-loader-windows-0.3.0-x64.jar",
|
||||
"sha1": "629fca32417d6ec489cef8b2cbd0827131ec6801",
|
||||
"size": 27174940
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"windows-x86": {
|
||||
"mesa-loader": {
|
||||
"name": "org.glavo:mesa-loader-windows:0.2.0:x86",
|
||||
"name": "org.glavo:mesa-loader-windows:0.3.0:x86",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/glavo/mesa-loader-windows/0.2.0/mesa-loader-windows-0.2.0-x86.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/0.2.0/mesa-loader-windows-0.2.0-x86.jar",
|
||||
"sha1": "93c0b9b7382d984e713d3aa299ceb337fde2897b",
|
||||
"size": 18273286
|
||||
"path": "org/glavo/mesa-loader-windows/0.3.0/mesa-loader-windows-0.3.0-x86.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/0.3.0/mesa-loader-windows-0.3.0-x86.jar",
|
||||
"sha1": "d25e0cdf5c5eb182acc9b93f700e0e6d8de36283",
|
||||
"size": 22528549
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4398,7 +4387,18 @@
|
||||
"com.mojang:text2speech:1.10.3:natives": null,
|
||||
"com.mojang:text2speech:1.11.3:natives": null,
|
||||
"com.mojang:text2speech:1.12.4:natives": null,
|
||||
"com.mojang:text2speech:1.13.9:natives-windows": null
|
||||
"com.mojang:text2speech:1.13.9:natives-windows": null,
|
||||
"mesa-loader": {
|
||||
"name": "org.glavo:mesa-loader-windows:0.3.0:arm64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/glavo/mesa-loader-windows/0.3.0/mesa-loader-windows-0.3.0-arm64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/glavo/mesa-loader-windows/0.3.0/mesa-loader-windows-0.3.0-arm64.jar",
|
||||
"sha1": "1986490c6fbe950e64018c2fb62c8ecf77a247ed",
|
||||
"size": 24082103
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"osx-arm64": {
|
||||
"org.lwjgl.lwjgl:lwjgl-platform:2.9.1-nightly-20130708-debug3:natives": {
|
||||
@@ -5274,7 +5274,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl:3.3.1:natives": {
|
||||
"org.lwjgl:lwjgl:3.3.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
@@ -5296,7 +5296,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-jemalloc:3.3.1:natives": {
|
||||
"org.lwjgl:lwjgl-jemalloc:3.3.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-jemalloc:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
@@ -5318,7 +5318,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-openal:3.3.1:natives": {
|
||||
"org.lwjgl:lwjgl-openal:3.3.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-openal:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
@@ -5340,7 +5340,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-opengl:3.3.1:natives": {
|
||||
"org.lwjgl:lwjgl-opengl:3.3.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-opengl:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
@@ -5362,7 +5362,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-glfw:3.3.1:natives": {
|
||||
"org.lwjgl:lwjgl-glfw:3.3.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-glfw:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
@@ -5384,7 +5384,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-stb:3.3.1:natives": {
|
||||
"org.lwjgl:lwjgl-stb:3.3.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-stb:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
@@ -5406,7 +5406,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-tinyfd:3.3.1:natives": {
|
||||
"org.lwjgl:lwjgl-tinyfd:3.3.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-tinyfd:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
@@ -5428,7 +5428,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl:3.3.2:natives": {
|
||||
"org.lwjgl:lwjgl:3.3.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
@@ -5450,7 +5450,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-jemalloc:3.3.2:natives": {
|
||||
"org.lwjgl:lwjgl-jemalloc:3.3.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-jemalloc:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
@@ -5472,7 +5472,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-openal:3.3.2:natives": {
|
||||
"org.lwjgl:lwjgl-openal:3.3.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-openal:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
@@ -5494,7 +5494,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-opengl:3.3.2:natives": {
|
||||
"org.lwjgl:lwjgl-opengl:3.3.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-opengl:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
@@ -5516,7 +5516,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-glfw:3.3.2:natives": {
|
||||
"org.lwjgl:lwjgl-glfw:3.3.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-glfw:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
@@ -5538,7 +5538,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-stb:3.3.2:natives": {
|
||||
"org.lwjgl:lwjgl-stb:3.3.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-stb:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
@@ -5560,7 +5560,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-tinyfd:3.3.2:natives": {
|
||||
"org.lwjgl:lwjgl-tinyfd:3.3.2:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-tinyfd:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
@@ -5571,6 +5571,182 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl:3.3.3": {
|
||||
"name": "org.lwjgl:lwjgl:3.3.4",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4.jar",
|
||||
"sha1": "b86c3e4832426e8a6b466013b7cb34b40e9ce956",
|
||||
"size": 800127
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl:3.3.3:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-freebsd.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.3.4/lwjgl-3.3.4-natives-freebsd.jar",
|
||||
"sha1": "610d14530e637564d97d74af7cb98a737e70b77b",
|
||||
"size": 96209
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-jemalloc:3.3.3": {
|
||||
"name": "org.lwjgl:lwjgl-jemalloc:3.3.4",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4.jar",
|
||||
"sha1": "e3f5dcb8e13f3a5ed3f740fd30a114cee2a80bc4",
|
||||
"size": 46430
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-jemalloc:3.3.3:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-jemalloc:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-freebsd.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.3.4/lwjgl-jemalloc-3.3.4-natives-freebsd.jar",
|
||||
"sha1": "5ee27f3bad4715067cef0630682da4bb5a1b88ac",
|
||||
"size": 157297
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-openal:3.3.3": {
|
||||
"name": "org.lwjgl:lwjgl-openal:3.3.4",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4.jar",
|
||||
"sha1": "9b74d3ea380c83353d42af43ad9659e04dabe84a",
|
||||
"size": 113103
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-openal:3.3.3:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-openal:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-freebsd.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.3.4/lwjgl-openal-3.3.4-natives-freebsd.jar",
|
||||
"sha1": "3863f8268f5515c27f1364257f8a018f0c6afa79",
|
||||
"size": 597486
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-opengl:3.3.3": {
|
||||
"name": "org.lwjgl:lwjgl-opengl:3.3.4",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4.jar",
|
||||
"sha1": "2852ac7d9f6fc71349f1ce28e2708ff1977f18af",
|
||||
"size": 931960
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-opengl:3.3.3:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-opengl:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-freebsd.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.3.4/lwjgl-opengl-3.3.4-natives-freebsd.jar",
|
||||
"sha1": "579071d2a3714f5662522f7d3edf58e941580587",
|
||||
"size": 81028
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-glfw:3.3.3": {
|
||||
"name": "org.lwjgl:lwjgl-glfw:3.3.4",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4.jar",
|
||||
"sha1": "7e46ecdec85db8738053cfde1414352cd62dab74",
|
||||
"size": 147044
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-glfw:3.3.3:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-glfw:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-freebsd.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.3.4/lwjgl-glfw-3.3.4-natives-freebsd.jar",
|
||||
"sha1": "f67b9b6c29451d8fea66db17aaba2f65e908c7e9",
|
||||
"size": 104415
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-stb:3.3.3": {
|
||||
"name": "org.lwjgl:lwjgl-stb:3.3.4",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4.jar",
|
||||
"sha1": "5821735d5ef23f6da8542887344e57eb181b7cac",
|
||||
"size": 143112
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-stb:3.3.3:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-stb:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-freebsd.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.3.4/lwjgl-stb-3.3.4-natives-freebsd.jar",
|
||||
"sha1": "f5551338a1e2035ff747053f0e985dc93db1235c",
|
||||
"size": 226093
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-tinyfd:3.3.3": {
|
||||
"name": "org.lwjgl:lwjgl-tinyfd:3.3.4",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4.jar",
|
||||
"sha1": "2d73789ffd8962b38d9d599cc38b2383ce818c7a",
|
||||
"size": 15928
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-tinyfd:3.3.3:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-tinyfd:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-freebsd.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.3.4/lwjgl-tinyfd-3.3.4-natives-freebsd.jar",
|
||||
"sha1": "acd5e1b9b9b99ce4d21867058ee468ee45a859e5",
|
||||
"size": 40104
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-freetype:3.3.3": {
|
||||
"name": "org.lwjgl:lwjgl-freetype:3.3.4",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4.jar",
|
||||
"sha1": "23f7bf165068ef2ca80ae1b79fd905af20498600",
|
||||
"size": 453489
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-freetype:3.3.3:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-freetype:3.3.4:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4-natives-freebsd.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-freetype/3.3.4/lwjgl-freetype-3.3.4-natives-freebsd.jar",
|
||||
"sha1": "67d6775292771087cb3d5ba1239bf9bf42fb3bd7",
|
||||
"size": 1176759
|
||||
}
|
||||
}
|
||||
},
|
||||
"net.java.jinput:jinput-platform:2.0.5:natives": null,
|
||||
"com.mojang:text2speech:1.10.3:natives": null,
|
||||
"com.mojang:text2speech:1.11.3:natives": null,
|
||||
|
||||
@@ -82,25 +82,25 @@
|
||||
"module": "javafx.base",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-base",
|
||||
"version": "17.0.13",
|
||||
"version": "19.0.2.1",
|
||||
"classifier": "mac",
|
||||
"sha1": "e1744403a6691aaa6b65a7e9b4ff9aad98831bb1"
|
||||
"sha1": "73e422d8426aaa23e8c712b9f4d9bebf70d3bfb9"
|
||||
},
|
||||
{
|
||||
"module": "javafx.graphics",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-graphics",
|
||||
"version": "17.0.13",
|
||||
"version": "19.0.2.1",
|
||||
"classifier": "mac",
|
||||
"sha1": "dbe733470094abee56560067d0fea3ad24991d2b"
|
||||
"sha1": "6ab6f3e23421fcfa04e692d9d26a8f55a830c114"
|
||||
},
|
||||
{
|
||||
"module": "javafx.controls",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-controls",
|
||||
"version": "17.0.13",
|
||||
"version": "19.0.2.1",
|
||||
"classifier": "mac",
|
||||
"sha1": "0d43a776d6c9e2622e3581ed04501e2cadcd6e65"
|
||||
"sha1": "b7786b1b63e741c0e234829825fae5fef9d96c31"
|
||||
}
|
||||
],
|
||||
"osx-arm64": [
|
||||
@@ -108,25 +108,25 @@
|
||||
"module": "javafx.base",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-base",
|
||||
"version": "17.0.13",
|
||||
"version": "19.0.2.1",
|
||||
"classifier": "mac-aarch64",
|
||||
"sha1": "d4dc7d2864c07b56a27e39fa5691856ddccf0ba4"
|
||||
"sha1": "1d0d887c492330ed527b0614d115a4f32d2d05a4"
|
||||
},
|
||||
{
|
||||
"module": "javafx.graphics",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-graphics",
|
||||
"version": "17.0.13",
|
||||
"version": "19.0.2.1",
|
||||
"classifier": "mac-aarch64",
|
||||
"sha1": "d6a49f19e90db56c134757d1bbab4935de075d45"
|
||||
"sha1": "64db28799e61e0f8f51e471c732599b2a6961803"
|
||||
},
|
||||
{
|
||||
"module": "javafx.controls",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-controls",
|
||||
"version": "17.0.13",
|
||||
"version": "19.0.2.1",
|
||||
"classifier": "mac-aarch64",
|
||||
"sha1": "ad564497edc49f7a6135c3b52d360962e19a2539"
|
||||
"sha1": "3a14bd5f3ebe45d344c1f2bade0fe074e6ea2a83"
|
||||
}
|
||||
],
|
||||
"linux-x86_64": [
|
||||
|
||||
@@ -12,8 +12,8 @@ dependencies {
|
||||
api("com.github.steveice10:opennbt:1.5")
|
||||
api("org.nanohttpd:nanohttpd:2.3.1")
|
||||
api("org.apache.commons:commons-compress:1.25.0")
|
||||
api("org.jsoup:jsoup:1.18.1")
|
||||
compileOnlyApi("org.jetbrains:annotations:24.1.0")
|
||||
api("org.jsoup:jsoup:1.18.3")
|
||||
compileOnlyApi("org.jetbrains:annotations:26.0.1")
|
||||
|
||||
if (JavaVersion.current().isJava8) {
|
||||
org.gradle.internal.jvm.Jvm.current().toolsJar?.let {
|
||||
|
||||
@@ -109,7 +109,7 @@ public class OAuth {
|
||||
options.callback.openBrowser(deviceTokenResponse.verificationURI);
|
||||
|
||||
long startTime = System.nanoTime();
|
||||
int interval = deviceTokenResponse.interval;
|
||||
long interval = TimeUnit.MILLISECONDS.convert(deviceTokenResponse.interval, TimeUnit.SECONDS);
|
||||
|
||||
while (true) {
|
||||
Thread.sleep(Math.max(interval, 1));
|
||||
@@ -138,7 +138,7 @@ public class OAuth {
|
||||
}
|
||||
|
||||
if ("slow_down".equals(tokenResponse.error)) {
|
||||
interval += 5;
|
||||
interval += 5000;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ public final class MicrosoftAccount extends OAuthAccount {
|
||||
|
||||
@Override
|
||||
public AuthInfo logIn() throws AuthenticationException {
|
||||
if (!authenticated) {
|
||||
if (!authenticated || System.currentTimeMillis() > session.getNotAfter()) {
|
||||
if (service.validate(session.getNotAfter(), session.getTokenType(), session.getAccessToken())) {
|
||||
authenticated = true;
|
||||
} else {
|
||||
|
||||
@@ -72,6 +72,7 @@ public final class BMCLAPIDownloadProvider implements DownloadProvider {
|
||||
pair("https://files.minecraftforge.net/maven", apiRoot + "/maven"),
|
||||
pair("https://maven.minecraftforge.net", apiRoot + "/maven"),
|
||||
pair("https://maven.neoforged.net/releases/net/neoforged/forge", apiRoot + "/maven/net/neoforged/forge"),
|
||||
pair("https://maven.neoforged.net/releases/net/neoforged/neoforge", apiRoot + "/maven/net/neoforged/neoforge"),
|
||||
pair("http://dl.liteloader.com/versions/versions.json", apiRoot + "/maven/com/mumfrey/liteloader/versions.json"),
|
||||
pair("http://dl.liteloader.com/versions", apiRoot + "/maven"),
|
||||
pair("https://meta.fabricmc.net", apiRoot + "/fabric-meta"),
|
||||
|
||||
@@ -170,6 +170,7 @@ public final class LibraryAnalyzer implements Iterable<LibraryAnalyzer.LibraryMa
|
||||
Version resolvedVersion = version.resolve(provider);
|
||||
String mainClass = resolvedVersion.getMainClass();
|
||||
return mainClass != null && (LAUNCH_WRAPPER_MAIN.equals(mainClass)
|
||||
|| mainClass.startsWith("net.minecraftforge")
|
||||
|| mainClass.startsWith("net.fabricmc")
|
||||
|| mainClass.startsWith("org.quiltmc")
|
||||
|| mainClass.startsWith("cpw.mods"));
|
||||
@@ -215,11 +216,6 @@ public final class LibraryAnalyzer implements Iterable<LibraryAnalyzer.LibraryMa
|
||||
|
||||
@Override
|
||||
protected String patchVersion(Version gameVersion, String libraryVersion) {
|
||||
Matcher matcher = NEO_FORGE_VERSION_MATCHER.matcher(libraryVersion);
|
||||
if (matcher.find()) {
|
||||
return matcher.group("forge");
|
||||
}
|
||||
|
||||
String res = scanVersion(gameVersion);
|
||||
if (res != null) {
|
||||
return res;
|
||||
@@ -232,6 +228,11 @@ public final class LibraryAnalyzer implements Iterable<LibraryAnalyzer.LibraryMa
|
||||
}
|
||||
}
|
||||
|
||||
Matcher matcher = NEO_FORGE_VERSION_MATCHER.matcher(libraryVersion);
|
||||
if (matcher.find()) {
|
||||
return matcher.group("forge");
|
||||
}
|
||||
|
||||
return super.patchVersion(gameVersion, libraryVersion);
|
||||
}
|
||||
|
||||
@@ -247,12 +248,15 @@ public final class LibraryAnalyzer implements Iterable<LibraryAnalyzer.LibraryMa
|
||||
|
||||
for (int i = 0; i < gameArguments.size() - 1; i++) {
|
||||
Argument argument = gameArguments.get(i);
|
||||
if (argument instanceof StringArgument && "--fml.neoForgeVersion".equals(((StringArgument) argument).getArgument())) {
|
||||
Argument next = gameArguments.get(i + 1);
|
||||
if (next instanceof StringArgument) {
|
||||
return ((StringArgument) next).getArgument();
|
||||
if (argument instanceof StringArgument) {
|
||||
String argumentValue = ((StringArgument) argument).getArgument();
|
||||
if ("--fml.neoForgeVersion".equals(argumentValue) || "--fml.forgeVersion".equals(argumentValue)) {
|
||||
Argument next = gameArguments.get(i + 1);
|
||||
if (next instanceof StringArgument) {
|
||||
return ((StringArgument) next).getArgument();
|
||||
}
|
||||
return null; // Normally, there should not be two --fml.neoForgeVersion argument.
|
||||
}
|
||||
return null; // Normally, there should not be two --fml.neoForgeVersion argument.
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -33,7 +33,7 @@ public final class GameJavaVersion {
|
||||
public static final GameJavaVersion LATEST = JAVA_21;
|
||||
|
||||
public static GameJavaVersion getMinimumJavaVersion(GameVersionNumber gameVersion) {
|
||||
if (gameVersion.compareTo("1.21") >= 0)
|
||||
if (gameVersion.compareTo("1.20.5") >= 0)
|
||||
return JAVA_21;
|
||||
if (gameVersion.compareTo("1.18") >= 0)
|
||||
return JAVA_17;
|
||||
@@ -59,21 +59,6 @@ public final class GameJavaVersion {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isSupportedPlatform(Platform platform) {
|
||||
OperatingSystem os = platform.getOperatingSystem();
|
||||
Architecture arch = platform.getArchitecture();
|
||||
switch (arch) {
|
||||
case X86:
|
||||
return os == OperatingSystem.WINDOWS || os == OperatingSystem.LINUX;
|
||||
case X86_64:
|
||||
return os == OperatingSystem.WINDOWS || os == OperatingSystem.LINUX || os == OperatingSystem.OSX;
|
||||
case ARM64:
|
||||
return os == OperatingSystem.WINDOWS || os == OperatingSystem.OSX;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<GameJavaVersion> getSupportedVersions(Platform platform) {
|
||||
OperatingSystem operatingSystem = platform.getOperatingSystem();
|
||||
Architecture architecture = platform.getArchitecture();
|
||||
|
||||
@@ -62,7 +62,14 @@ public enum JavaVersionConstraint {
|
||||
return analyzer != null && analyzer.has(LibraryAnalyzer.LibraryType.FORGE);
|
||||
}
|
||||
},
|
||||
MODDED_JAVA_17(false, GameVersionNumber.atLeast("1.18"), VersionNumber.between("17", "17.999")) {
|
||||
MODDED_JAVA_17(false, GameVersionNumber.between("1.18", "1.20.4"), VersionNumber.between("17", "17.999")) {
|
||||
@Override
|
||||
protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,
|
||||
@Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) {
|
||||
return analyzer != null && analyzer.has(LibraryAnalyzer.LibraryType.FORGE);
|
||||
}
|
||||
},
|
||||
MODDED_JAVA_21(false, GameVersionNumber.atLeast("1.20.5"), VersionNumber.between("21", "21.999")) {
|
||||
@Override
|
||||
protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,
|
||||
@Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) {
|
||||
|
||||
@@ -18,25 +18,13 @@
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.gson.JsonMap;
|
||||
import org.jackhuang.hmcl.util.gson.Validation;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
@@ -229,7 +217,42 @@ public class Version implements Comparable<Version>, Validation {
|
||||
|
||||
public AssetIndexInfo getAssetIndex() {
|
||||
String assetsId = assets == null ? "legacy" : assets;
|
||||
return assetIndex == null ? new AssetIndexInfo(assetsId, Constants.DEFAULT_INDEX_URL + assetsId + ".json") : assetIndex;
|
||||
|
||||
if (assetIndex == null) {
|
||||
String hash;
|
||||
switch (assetsId) {
|
||||
case "1.8":
|
||||
hash = "f6ad102bcaa53b1a58358f16e376d548d44933ec";
|
||||
break;
|
||||
case "14w31a":
|
||||
hash = "10a2a0e75b03cfb5a7196abbdf43b54f7fa61deb";
|
||||
break;
|
||||
case "14w25a":
|
||||
hash = "32ff354a3be1c4dd83027111e6d79ee4d701d2c0";
|
||||
break;
|
||||
case "1.7.4":
|
||||
hash = "545510a60f526b9aa8a38f9c0bc7a74235d21675";
|
||||
break;
|
||||
case "1.7.10":
|
||||
hash = "1863782e33ce7b584fc45b037325a1964e095d3e";
|
||||
break;
|
||||
case "1.7.3":
|
||||
hash = "f6cf726f4747128d13887010c2cbc44ba83504d9";
|
||||
break;
|
||||
case "pre-1.6":
|
||||
hash = "3d8e55480977e32acd9844e545177e69a52f594b";
|
||||
break;
|
||||
case "legacy":
|
||||
default:
|
||||
assetsId = "legacy";
|
||||
hash = "770572e819335b6c0a053f8378ad88eda189fc14";
|
||||
}
|
||||
|
||||
String url = Constants.DEFAULT_INDEX_URL + hash + "/" + assetsId + ".json";
|
||||
return new AssetIndexInfo(assetsId, url);
|
||||
} else {
|
||||
return assetIndex;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean appliesToCurrentEnvironment() {
|
||||
|
||||
@@ -50,6 +50,8 @@ import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
*/
|
||||
public class DefaultLauncher extends Launcher {
|
||||
|
||||
private final LibraryAnalyzer analyzer;
|
||||
|
||||
public DefaultLauncher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options) {
|
||||
this(repository, version, authInfo, options, null);
|
||||
}
|
||||
@@ -60,6 +62,8 @@ public class DefaultLauncher extends Launcher {
|
||||
|
||||
public DefaultLauncher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options, ProcessListener listener, boolean daemon) {
|
||||
super(repository, version, authInfo, options, listener, daemon);
|
||||
|
||||
this.analyzer = LibraryAnalyzer.analyze(version, repository.getGameVersion(version).orElse(null));
|
||||
}
|
||||
|
||||
private Command generateCommandLine(File nativeFolder) throws IOException {
|
||||
@@ -496,12 +500,18 @@ public class DefaultLauncher extends Launcher {
|
||||
break;
|
||||
case ZINK:
|
||||
env.put("MESA_LOADER_DRIVER_OVERRIDE", "zink");
|
||||
/*
|
||||
* The amdgpu DDX is missing support for modifiers, causing Zink to fail.
|
||||
* Disable DRI3 to workaround this issue.
|
||||
*
|
||||
* Link: https://gitlab.freedesktop.org/mesa/mesa/-/issues/10093
|
||||
*/
|
||||
env.put("LIBGL_KOPPER_DRI2", "1");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version, repository.getGameVersion(version).orElse(null));
|
||||
if (analyzer.has(LibraryAnalyzer.LibraryType.FORGE)) {
|
||||
env.put("INST_FORGE", "1");
|
||||
}
|
||||
|
||||
@@ -160,12 +160,17 @@ public final class ForgeNewModMetadata {
|
||||
|
||||
private static ModLoaderType analyzeLoader(Toml toml, String modID, int loaderACC, ModLoaderType defaultLoader) throws IOException {
|
||||
List<HashMap<String, Object>> dependencies = toml.getList("dependencies." + modID);
|
||||
if (dependencies != null) {
|
||||
for (HashMap<String, Object> dependency : dependencies) {
|
||||
switch ((String) dependency.get("modId")) {
|
||||
case "forge": return checkLoaderACC(loaderACC, ACC_FORGE, ModLoaderType.FORGE);
|
||||
case "neoforge": return checkLoaderACC(loaderACC, ACC_NEO_FORGED, ModLoaderType.NEO_FORGED);
|
||||
}
|
||||
if (dependencies == null) {
|
||||
dependencies = toml.getList("dependencies"); // ??? I have no idea why some of the Forge mods use [[dependencies]]
|
||||
if (dependencies == null) {
|
||||
return defaultLoader;
|
||||
}
|
||||
}
|
||||
|
||||
for (HashMap<String, Object> dependency : dependencies) {
|
||||
switch ((String) dependency.get("modId")) {
|
||||
case "forge": return checkLoaderACC(loaderACC, ACC_FORGE, ModLoaderType.FORGE);
|
||||
case "neoforge": return checkLoaderACC(loaderACC, ACC_NEO_FORGED, ModLoaderType.NEO_FORGED);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.mod.modrinth;
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.game.DefaultGameRepository;
|
||||
import org.jackhuang.hmcl.mod.ModManager;
|
||||
import org.jackhuang.hmcl.mod.ModpackCompletionException;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
@@ -27,6 +28,7 @@ import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
@@ -41,6 +43,7 @@ public class ModrinthCompletionTask extends Task<Void> {
|
||||
|
||||
private final DefaultDependencyManager dependency;
|
||||
private final DefaultGameRepository repository;
|
||||
private final ModManager modManager;
|
||||
private final String version;
|
||||
private ModrinthManifest manifest;
|
||||
private final List<Task<?>> dependencies = new ArrayList<>();
|
||||
@@ -69,6 +72,7 @@ public class ModrinthCompletionTask extends Task<Void> {
|
||||
public ModrinthCompletionTask(DefaultDependencyManager dependencyManager, String version, ModrinthManifest manifest) {
|
||||
this.dependency = dependencyManager;
|
||||
this.repository = dependencyManager.getGameRepository();
|
||||
this.modManager = repository.getModManager(version);
|
||||
this.version = version;
|
||||
this.manifest = manifest;
|
||||
|
||||
@@ -99,18 +103,28 @@ public class ModrinthCompletionTask extends Task<Void> {
|
||||
if (manifest == null)
|
||||
return;
|
||||
|
||||
Path runDirectory = repository.getRunDirectory(version).toPath();
|
||||
Path runDirectory = repository.getRunDirectory(version).toPath().toAbsolutePath().normalize();
|
||||
Path modsDirectory = runDirectory.resolve("mods");
|
||||
|
||||
for (ModrinthManifest.File file : manifest.getFiles()) {
|
||||
if (file.getEnv() != null && file.getEnv().getOrDefault("client", "required").equals("unsupported"))
|
||||
continue;
|
||||
Path filePath = runDirectory.resolve(file.getPath());
|
||||
if (!Files.exists(filePath) && !file.getDownloads().isEmpty()) {
|
||||
FileDownloadTask task = new FileDownloadTask(file.getDownloads().get(0), filePath.toFile());
|
||||
task.setCacheRepository(dependency.getCacheRepository());
|
||||
task.setCaching(true);
|
||||
dependencies.add(task.withCounter("hmcl.modpack.download"));
|
||||
}
|
||||
if (file.getDownloads().isEmpty())
|
||||
continue;
|
||||
|
||||
Path filePath = runDirectory.resolve(file.getPath()).toAbsolutePath().normalize();
|
||||
if (!filePath.startsWith(runDirectory))
|
||||
throw new IOException("Unsecure path: " + file.getPath());
|
||||
|
||||
if (Files.exists(filePath))
|
||||
continue;
|
||||
if (modsDirectory.equals(filePath.getParent()) && this.modManager.hasSimpleMod(FileUtils.getName(filePath)))
|
||||
continue;
|
||||
|
||||
FileDownloadTask task = new FileDownloadTask(file.getDownloads(), filePath.toFile());
|
||||
task.setCacheRepository(dependency.getCacheRepository());
|
||||
task.setCaching(true);
|
||||
dependencies.add(task.withCounter("hmcl.modpack.download"));
|
||||
}
|
||||
|
||||
if (!dependencies.isEmpty()) {
|
||||
|
||||
Loaded 100 of 119 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user