Compare commits

...
111 Commits
Author SHA1 Message Date
Glavo 33b791f2b0 更新日期时间格式 (#3610)
* Revert "improv(i18n): refine time formats on ui (#3608)"

This reverts commit 741f65d37c.

* update
2025-02-12 19:37:05 +08:00
3gf8jv4dv de01946ab2 fix(locale): fix a typo in zh-TW language (#3606) 2025-02-12 17:35:03 +08:00
3gf8jv4dv 5526bac70e fix(locale): change Socks to SOCKS (#3607)
ref: https://en.wikipedia.org/wiki/SOCKS
2025-02-12 17:33:31 +08:00
3gf8jv4dv 741f65d37c improv(i18n): refine time formats on ui (#3608)
- unify time formats using DateTimeFormatter, deprecate `world.time`
- change FormatStyle to make the time detail appropriate
- fix incorrect time format in English locale
2025-02-12 17:32:36 +08:00
Haowei Wen ecafc9d6f0 Fix height computation in ComponentListCell 2025-02-11 23:51:50 +08:00
Glavo 8795d522b0 支持临时隐藏预览版提示 (#3603) 2025-02-11 19:00:03 +08:00
Glavo d8e7e0e97e 更新 mod 数据 (#3601)
* 更新 mod 数据

* update

* update

* update
2025-02-10 22:04:48 +08:00
Glavo 8236dfc7e5 Fix #3595: patchNative may throw NPE when gameVersion is null (#3602) 2025-02-10 19:59:25 +08:00
Glavo 8a15b7d274 修复使用自定义字体时未正确设置字重的问题 (#3598) 2025-02-10 00:41:31 +08:00
3gf8jv4dv fce4c13515 fix(locale): fix zh-tw locale for game crash window buttons (#3596) 2025-02-09 09:28:00 +08:00
Glavo 2f36506099 Fix #3508: Java 下载对话框中切换包类型时不应重置版本字段 (#3594)
* Fix #3508: Java 下载对话框中切换包类型时不应重置版本字段

* fix checkstyle
2025-02-08 18:02:46 +08:00
Glavo 5e60eaf595 修复 macOS 平台 HMCLauncher.sh 不会显示 Java 下载页面的问题 (#3549) 2025-01-29 07:35:19 +08:00
Glavo f70fb4ae08 Update README (#3548) 2025-01-29 07:27:17 +08:00
3gf8jv4dv 38adb48051 Fix qq badge in readme (#3544)
ref: https://shields.io/blog/simple-icons-14
2025-01-25 16:38:55 +08:00
Glavo f972c49762 Fix #3539: 安装 MultiMC 整合包后应当设置图标 (#3541) 2025-01-24 09:11:22 +08:00
Glavo 5afeb3d019 优化 HMCLauncher.sh (#3538) 2025-01-23 18:21:29 +08:00
Burning_TNT 38897fbd2a Fix #3063 修复本地模组管理界面水平滚动条显示异常的问题 (#3463) 2025-01-23 13:51:55 +08:00
ZkiteflyandGlavo 0562042218 Update HMCLauncher.sh (#3265)
* Update HMCLauncher.sh

* update

* update

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
2025-01-23 13:17:33 +08:00
Glavo c5e7ee8aef 优化对 Linux RISC-V 64 平台的支持 (#3536) 2025-01-23 10:23:27 +08:00
Glavo 35b288b800 Fix #3529: 修复在 FreeBSD x86-64 平台未正确替换 LWJGL 本地库的问题 (#3531)
* Fix #3529: 修复在 FreeBSD x86-64 平台未正确替换 LWJGL 本地库的问题

* update

* update

* update
2025-01-20 14:03:11 +08:00
Zkitefly a609d32081 更新版权年份至2025 (#3527) 2025-01-16 11:26:31 +08:00
Haowei Wen 72d8b75643 模组列表页中按下 Esc 时取消选中 (#3523)
* 模组列表页中按下 Esc 时取消选中

目前在模组列表页中选中模组后,按下 Esc 是不会有任何反应的,因为 [ListView](https://github.com/openjdk/jfx/blob/5b074c4c2b4f539301813322a6589cec28121553/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/behavior/ListViewBehavior.java#L108) 会处理这个事件,认为是取消编辑,而不论是否处于编辑模式中。

这个 PR 添加了 `FXUtils.ignoreEvent()` 函数,用于忽略掉对特定事件的处理。然后,我们就可以让 ListView 不处理 Esc 按下事件,然后在模组页添加 Esc 处理逻辑(必须让 ListView 忽略掉这个事件,不然就会被它优先处理)。

理论上所有用了 ListView / JFXListView 的地方都会有这个问题,即当焦点在 ListView 中时,按下 Esc 不会触发正常的事件处理(如返回上一页、关闭对话框)。比如在游戏下载页面中,对某个版本按下右键后,焦点就会转移到其上,此时再按 Esc 就毫无反应。

* 修复 Esc 在版本下载页/模组下载页/数据包页不工作
2025-01-16 11:15:29 +08:00
3gf8jv4dvandyushijinhun 01f9a452f1 Update ISSUE_TEMPLATE [skip ci] (#3446)
* Update ISSUE_TEMPLATE [skip ci]

* Apply suggestions from yushijinhun

Co-authored-by: yushijinhun <yushijinhun@gmail.com>

---------

Co-authored-by: yushijinhun <yushijinhun@gmail.com>
2025-01-06 03:40:49 +08:00
3gf8jv4dvandyushijinhun f2edc8001c Revise Spanish & English (#3465)
* Revise Spanish & English

Overhaul for Spanish. I cannot guarantee it is 100% correct, but I
promise to proofread it. During the overhaul some more mistakes in the
English language were discovered, so let's fix it.

All translations have been revised except for the `game.crash.reason`
series.

* Apply suggestions from yushijinhun

Co-authored-by: yushijinhun <yushijinhun@gmail.com>

---------

Co-authored-by: yushijinhun <yushijinhun@gmail.com>
2025-01-06 03:37:19 +08:00
3gf8jv4dvandyushijinhun d2df515325 Revise README [skip ci] (#3447)
* Revise README [skip ci]

* Apply suggestions from yushijinhun

Co-authored-by: yushijinhun <yushijinhun@gmail.com>

---------

Co-authored-by: yushijinhun <yushijinhun@gmail.com>
2025-01-06 03:32:42 +08:00
3gf8jv4dv 95e39fe87a gha: revise release description [skip ci] (#3474)
unify text style
2025-01-06 03:12:13 +08:00
lijiaxuanand3gf8jv4dv 662bf1a9d4 Update I18N.properties English Translations (#3477)
* Update I18N.properties English Translations

更新了英文语言翻译文件,修复了一些语法错误并且让一些句子更加通顺

* Update I18N.properties

Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com>

* Update I18N.properties

Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com>

* Update I18N.properties

Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com>

* Update I18N.properties

Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com>

* Update HMCL/src/main/resources/assets/lang/I18N.properties

Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com>

* Update I18N.properties

* Update HMCL/src/main/resources/assets/lang/I18N.properties

Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com>

* Update HMCL/src/main/resources/assets/lang/I18N.properties

Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com>

---------

Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com>
2025-01-06 02:15:58 +08:00
Zkiteflyand3gf8jv4dv bfda297838 检查安装时的非ASCII字符名称 (#3244)
* 检查安装时的非ASCII字符名称

* i18n

* 添加导出模组列表信息功能

* Revert "添加导出模组列表信息功能"

This reverts commit 6505d1c284.

* Update HMCL/src/main/resources/assets/lang/I18N.properties

Co-authored-by: 3gf8jv4dv <158490944+3gf8jv4dv@users.noreply.github.com>

* Update HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties

Co-authored-by: 3gf8jv4dv <158490944+3gf8jv4dv@users.noreply.github.com>

* Update HMCL/src/main/resources/assets/lang/I18N_zh.properties

Co-authored-by: 3gf8jv4dv <158490944+3gf8jv4dv@users.noreply.github.com>

---------

Co-authored-by: 3gf8jv4dv <158490944+3gf8jv4dv@users.noreply.github.com>
2025-01-05 01:31:44 +08:00
ZkiteflyandGlavo d80f61e2e7 update getAssetIndex() (#3252)
* update getAssetIndex()

* update

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
2025-01-04 21:33:48 +08:00
Zkitefly 1f7b156423 Press ESC in search box to exit search (#3519) 2025-01-04 01:38:11 +08:00
Haowei Wen 56d20a5d08 Fix problems with window resizing in Linux 2025-01-04 01:35:36 +08:00
Haowei Wen b5cea83a8a Workaround for JDK-8344372
https://bugs.openjdk.org/browse/JDK-8344372
2025-01-03 20:15:03 +08:00
Haowei Wen 397edaec5d Press ESC in search box to exit search 2025-01-03 17:08:19 +08:00
Zkitefly 39b8de4824 fix 2025-01-03 17:08:19 +08:00
Zkitefly 5197157a97 添加搜索功能 2025-01-03 17:08:19 +08:00
Glavo d61826ba4c 优化 Release Note (#3516) 2025-01-01 05:37:30 +08:00
Glavo 85886da770 Bump Mesa to 24.3.2 (#3515)
* Bump Mesa to 24.3.2

* update
2025-01-01 05:14:14 +08:00
Haowei Wen 6f53da159a Refine DownloadPage
* Fix cursor is not hand when hovering on ModItem
* Fix clicking on the ModItem in ModVersion dialog would open a new dialog again
* Fix ESC doesn't work in ModVersion dialog
2024-12-31 19:56:38 +08:00
Haowei Wen 78e15d130b Update memory status every 1s, fix #3440
This commit also fixes Lang.getTimer() returns a non-daemon Timer,
preventing Java from exiting after closing the launcher.
2024-12-30 20:14:04 +08:00
Haowei Wen 7d12ef6b09 Fix memory slider indicator is covered by progress bar 2024-12-30 19:39:42 +08:00
Haowei Wen 260f4b6c5d Fix ESC not working in input dialog 2024-12-30 00:17:00 +08:00
Haowei Wen 104534d12f Fix style 2024-12-30 00:07:43 +08:00
Haowei Wen 81b3911dc9 Fix value in text field might not be saved when pressing ESC
https://github.com/HMCL-dev/HMCL/pull/1869#issuecomment-2335027530
2024-12-30 00:02:32 +08:00
Haowei Wen c5b8aabc57 Refactor VersionSetting.clone() & .addListener(), fix javaVersionType is not saved 2024-12-29 22:36:40 +08:00
Haowei Wen f0a064afdd Fix sometimes version specific setting is lost 2024-12-29 22:18:15 +08:00
Haowei Wen 8a4f6af193 Fix NPE due to the order of attaching specificSettingsListener 2024-12-29 21:40:09 +08:00
Haowei Wen e63ee49eaa Remove VersionSetting.global property as it's unused 2024-12-29 20:59:47 +08:00
Haowei Wen d5408c38b3 Copy global settings when specialize version setting 2024-12-29 20:18:58 +08:00
Haowei Wen 5fd52bb808 Move skin cache directory to hmcl dir, close #2970 2024-12-29 18:28:23 +08:00
白铭骢 (Mingcong Bai) cb2b3525b8 fix(DefaultLauncher): also set LIBGL_KOPPER_DRI2=1 for Zink mode (#3503)
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

Signed-off-by: Mingcong Bai <jeffbai@aosc.io>
2024-12-25 19:55:42 +08:00
Glavo 4c6fdf49de Bump JetBrains Annotations to 26.0.1 (#3493) 2024-12-13 00:29:34 +08:00
Burning_TNT 981b55a10e Fix #3456 在更新预览界面没法拖动启动器窗口 (#3464) 2024-12-10 16:20:02 +08:00
Glavo 8460df7d5d 修复 OAuth 登录时轮询过于频繁的问题 (#3490)
* 修复 OAuth 登录时轮询过于频繁的问题

* update
2024-12-09 17:47:42 +08:00
Glavo 727e6746d2 Update versions.txt (#3484)
* Update versions.txt

* update
2024-12-04 12:29:17 +08:00
Glavo 8e0f0e2a38 使用 ONE_PASS_BOX 阴影代替高斯模糊 (#3458)
* 使用 ONE_PASS_BOX 代替高斯模糊

* update

* update

* update
2024-12-02 21:24:58 +08:00
Glavo 3df52b0e8f Fix typo in check-update.yml (#3479) 2024-11-29 20:06:08 +08:00
Glavo 6f0e133bb0 添加 banshanjdk-8 下载地址 (#3476)
* 添加 banshanjdk-8 下载地址

* update

* fix i18n

* fix typo
2024-11-28 20:34:12 +08:00
Glavo 36da64f796 支持加载 WebP 格式的背景图片 (#3457)
* 支持加载 WebP 格式的背景图片

* update

* update

* update

* update
2024-11-28 00:35:08 +08:00
Glavo 4c242d0c80 不再生成 MD5 checksum (#3473) 2024-11-24 14:10:58 +08:00
Glavo 0a230ef6b5 优化 Release Note (#3472) 2024-11-23 19:48:46 +08:00
Burning_TNT c12ae9fdf8 Fix #3460 无法识别奇异的 Forge (Like) 模组 Metadata (#3470) 2024-11-21 21:22:20 +08:00
Glavo 773ef4282c Bump action-gh-release to v2 (#3469) 2024-11-17 22:09:29 +08:00
Burning_TNTandYELANDAOKONG 9edfe29659 实现 #2969: 启动前日志列出mods目录的jar文件 (#3125)
* Support #2969: 启动前日志列出mods目录的jar文件

* Support #2969: 启动前日志列出mods目录jar文件

* Support #2969: 启动前日志中使用字符画列出mods目录模组文件

* Support #2969: 启动前日志中使用字符画列出mods目录模组文件

* Support #2969: 启动前日志中使用字符画列出mods目录模组文件

* Support #2969: 日志列出mods目录时不再过滤文件后缀

* Support #2969: 启动时日志列出mods目录(代码优化)

* Support #2969: 启动时日志列出mods目录(不再列出Zip内容)

* Support #2969

* Fix: checkstyle

---------

Co-authored-by: YELANDAOKONG <yelandaokong@yldk.xyz>
2024-11-16 23:29:53 +08:00
Burning_TNT 5591d92e25 Fix #2991: 导入 PrismLauncher 格式整合包时 $.jvmArgs 参数未正确移除两侧引号 (#3030) 2024-11-15 22:49:30 +08:00
Glavo 65bdb29e71 在 macOS 上使用 JavaFX 19.0.2.1 (#3461) 2024-11-15 18:02:59 +08:00
Glavo 355df211db 在游戏下载列表中添加 Minecraft Wiki 链接 (#3435) 2024-11-14 17:49:27 +08:00
3gf8jv4dv fa66549f53 Revise English (#3453)
- Adjust some translation styles again
- Fix some missed mistakes
2024-11-14 09:59:15 +08:00
3gf8jv4dv 6e48dbd6c6 Fix dialog title when modpack download fails (#3451)
* Fix dialog title when modpack download fails

* Update
2024-11-14 09:58:14 +08:00
Zkitefly e3c7278759 Update check-update.yml (#3452) 2024-11-14 09:42:43 +08:00
3gf8jv4dv dd1829d8ca Check word 其它 → 其他 when checking translation (#3455)
There is some controversy over the use of this word.
In order to ensure consistency in formal texts, we
should unify them to more tried-and-true and commonly
used usages.

By checking at build time, it lets the translator
or proofreader correct the word on his/her own without
any special attention from the reviewer.
2024-11-14 09:27:45 +08:00
3gf8jv4dv 60ac16a3ea Revise Chinese (#3454)
* Revise Chinese

- Simplified Chinese only
- Minor revision

* oops...
2024-11-13 22:14:55 +08:00
Glavo b400a9a3ef 使最小化按钮居中 (#3450)
* 修复最小化按钮未居中的问题

* update
2024-11-12 14:24:15 +08:00
3gf8jv4dv c7cc2d0733 Fix missing i18n when checking for updates fails (#3444)
* Fix missing i18n when checking for updates fails

* Revise translation
2024-11-11 19:20:28 +08:00
Glavo 861223e909 清理 GameJavaVersion (#3441)
* 清理 GameJavaVersion

* update
2024-11-11 02:30:41 +08:00
Glavo 70ecbc734e Fix #3438: 修复 Minecraft 1.20.2+Forge 默认图标错误的问题 (#3439) 2024-11-10 23:19:14 +08:00
ZkiteflyandGlavo 2ca7e7c830 Fix #3200: 启动时不应补全 Modrinth 整合包中已禁用的 mod (#3308)
* Update ModrinthCompletionTask.java

* Update ModrinthCompletionTask.java

* Update ModrinthCompletionTask.java

* Update ModrinthCompletionTask.java

* Update ModrinthCompletionTask.java

* update

* update

* update

* update

* update

* update

* update

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
2024-11-10 21:27:36 +08:00
Wulian 9a0db56ac3 修复 Copyright © 显示为“漏”的bug (#3437)
Fixes #3436

Specify UTF-8 charset when reading `deps.json` file in `AboutPage.java`.

---

For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/HMCL-dev/HMCL/issues/3436?shareId=XXXX-XXXX-XXXX-XXXX).
2024-11-10 19:02:59 +08:00
Glavo c1ae5875ba 将 VERSION_ROOT 更新至 3.6 (#3431) 2024-11-10 13:01:50 +08:00
Glavo 1d0ae427bf Update JavaVersionConstraint: 1.20.5 requires Java 21 (#3434) 2024-11-09 19:39:09 +08:00
3gf8jv4dv 3dde2d19e2 Fix extra "=" in zh_CN launch.advice translation (#3430)
Closes #3429
2024-11-08 05:59:02 +08:00
3gf8jv4dv d5ec6d7b85 Revise i18n translation before releases (#3428)
- Fix some English grammar and text style
- Revise `Allow Cheats` to `Allow Commands/Cheats`
  - Due to the change from `Allow Cheats` to `Allow Commands` by Mojang in 24w04a.
  - https://minecraft.wiki/w/Java_Edition_24w04a
2024-11-07 21:37:59 +08:00
Glavo 13856b71a0 更新 HMCLauncher.exe (#3427) 2024-11-05 03:43:18 +08:00
Glavo c6658771ca 修复 HMCLauncher 无法正常识别 Windows on Arm 平台的问题 (#3425)
* update

* update

* update
2024-11-05 00:20:42 +08:00
Glavo 406f36584d 设置 FontComboBox 的最小宽度 (#3424) 2024-11-04 18:55:04 +08:00
Glavo 898750d058 修复对话框按钮自行移动的问题 (#3423)
* 修复对话框按钮自行移动的问题

* update

* update

* update
2024-11-04 18:41:14 +08:00
Glavo 09e9e934d3 Fix #3412: 修复点击前置模组时,模组下载对话框不会关闭的问题 (#3422) 2024-11-04 06:00:45 +08:00
Glavo 3d170267b7 Fix #3416: 修复打开游戏设置页时崩溃的问题 (#3421) 2024-11-04 05:34:28 +08:00
3gf8jv4dv 3ca7e3cf88 Fix incorrect i18n key and line breaks in es & ja (#3404)
* Fix incorrect i18n key and line breaks in es & ja

* fix
2024-11-03 17:03:41 +08:00
3gf8jv4dv 1378d1445a README & PLATFORM docs post-fixes (#3417) 2024-11-02 17:36:41 +08:00
3gf8jv4dv 16d304dbb4 en_US Post-fix (#3413)
* en_US Post-fix

Some minor fixes.

* fix `game.crash.feedback`

* oops...

* oops...

* Bunch of grammar fixes :/

* Another bunch fixes

* Minor fixes

* fix

* fix

* fix
2024-11-02 17:36:20 +08:00
3gf8jv4dv aff5db7759 Change the terminology %AppData% to %APPDATA% (#3420) 2024-11-02 02:27:54 +08:00
Glavo 0697aa9755 重命名包 moe.mickey.minecraft.skin.fx (#3419) 2024-11-02 01:05:52 +08:00
Glavo 995d3bd4b9 不再生成 bss 文件 (#3418) 2024-11-01 20:59:33 +08:00
Glavo 3bf5204e79 优化 Theme (#3414) 2024-11-01 02:12:16 +08:00
3gf8jv4dv 0b1ce6e94a Adjust language selection order (#3411)
Sort alphabetically by language code.
2024-10-31 23:30:44 +08:00
3gf8jv4dv a66ccd7385 Adjust Microsoft login related i18n (#3393)
* Adjust Microsoft login related i18n

- Adjust English and Chinese
- Fix typos and grammatical errors

* fix
2024-10-30 02:26:55 +08:00
3gf8jv4dv 7a3f125802 Adjust custom command hint (zh_CN only) (#3394)
I forgot to change the zh_CN part.
A half-width colon is better.
2024-10-30 02:22:34 +08:00
3gf8jv4dv 4a8dcce3d4 Adjust about page i18n (#3398)
* Adjust about page i18n

- Adjust all languages
- Add missing translations

* fix

* fix
2024-10-30 02:21:54 +08:00
3gf8jv4dv adcc3eb43f Adjust buttons, chat & crash i18n (#3401)
* Adjust buttons, chat & crash i18n

- Add missing translations

* fix

* fix

* fix

* fix ja

* fix es & ja
2024-10-30 02:20:39 +08:00
3gf8jv4dv 8bbd1a7cf4 Remove unused i18n keys in es, ja & ru (#3405) 2024-10-30 02:19:45 +08:00
Zkitefly 467ff26278 模组管理添加右键查看模组详情 (#3232)
* Update ModListPageSkin.java

* Update ModListPageSkin.java

* Update ModListPageSkin.java

* Update ModListPageSkin.java

* Update ModListPageSkin.java
2024-10-30 02:19:16 +08:00
Glavo d6a267afa4 优化 FXUtils.installTooltip (#3407)
* update

* Fix checkstyle

* update

* update

* update

* update

* update
2024-10-28 01:52:38 +08:00
Glavo 743e934436 优化“复制”图标 (#3406) 2024-10-28 00:29:29 +08:00
3gf8jv4dv f89ac2231a Adjust Java management i18n (#3402)
- Add missing languages.
2024-10-27 22:07:25 +08:00
3gf8jv4dv 1877a18ebb Fix en language missing a line break (#3403) 2024-10-27 22:07:03 +08:00
Glavo c99f85d9d5 缩小复制图标 (#3397)
* update

* update
2024-10-27 00:21:31 +08:00
Glavo 228942c60a 优化删除账号按钮图标 (#3396) 2024-10-26 23:49:43 +08:00
Glavo fecd567eab 简化 ContainerAnimations (#3395) 2024-10-26 23:34:41 +08:00
3gf8jv4dv 602840519b Change forgot password link (#3392) 2024-10-26 23:32:12 +08:00
959adf1075 更改主页 UI (#3129)
* Change root page UI

* Update HMCL/src/main/resources/assets/lang/I18N.properties

Co-authored-by: 3gf8jv4dv <158490944+3gf8jv4dv@users.noreply.github.com>

* Shorten I18N

* update

---------

Co-authored-by: 3gf8jv4dv <158490944+3gf8jv4dv@users.noreply.github.com>
Co-authored-by: Glavo <zjx001202@gmail.com>
2024-10-26 21:02:34 +08:00
Glavo 422f681fe6 Bump XZ to 1.10 (#3391) 2024-10-26 20:54:48 +08:00
127 changed files with 8844 additions and 3433 deletions

No files matched your search

+25 -19
View File
@@ -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
+4 -4
View File
@@ -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.
+10 -4
View File
@@ -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
+16 -10
View File
@@ -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: |
+1
View File
@@ -20,6 +20,7 @@ minecraft-exported-crash-info*
/HMCLCore/build/
/HMCLTransformerDiscoveryService/build/
/minecraft/libraries/HMCLTransformerDiscoveryService/build/
/buildSrc/build/
# idea
.idea
+3 -1
View File
@@ -1,2 +1,4 @@
/data.csv
/data.json
/data.json
/mod.json
/modpack.json
+19 -21
View File
@@ -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,
@@ -167,24 +171,6 @@ fun createExecutable(suffix: String, header: String) {
}
tasks.processResources {
fun convertToBSS(resource: String) {
doFirst {
val cssFile = File(projectDir, "src/main/resources/$resource")
val bssFile = File(projectDir, "build/compiled-resources/${resource.substring(0, resource.length - 4)}.bss")
bssFile.parentFile.mkdirs()
javaexec {
classpath = sourceSets["main"].compileClasspath
mainClass.set("com.sun.javafx.css.parser.Css2Bin")
args(cssFile, bssFile)
}
}
}
from("build/compiled-resources")
convertToBSS("assets/css/root.css")
convertToBSS("assets/css/blue.css")
into("META-INF/versions/11") {
from(sourceSets["java11"].output)
}
@@ -268,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"))
}
-123
View File
@@ -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!')
@@ -33,19 +33,19 @@ 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;
import org.jackhuang.hmcl.util.io.FileUtils;
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.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 +218,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 +251,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 +260,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 +272,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 +283,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 +299,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 +337,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 +359,12 @@ public class HMCLGameRepository extends DefaultGameRepository {
vs = createLocalVersionSetting(id);
if (vs == null)
return null;
vs.setUsesGlobal(false);
VersionIconType versionIcon = vs.getVersionIcon();
if (vs.isUsesGlobal()) {
PropertyUtils.copyProperties(profile.getGlobal(), vs);
vs.setUsesGlobal(false);
}
vs.setVersionIcon(versionIcon); // versionIcon is preserved
return vs;
}
@@ -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 {
@@ -178,6 +183,9 @@ public final class LauncherHelper {
.thenComposeAsync(() -> logIn(account).withStage("launch.state.logging_in"))
.thenComposeAsync(authInfo -> Task.supplyAsync(() -> {
LaunchOptions launchOptions = repository.getLaunchOptions(selectedVersion, javaVersionRef.get(), profile.getGameDir(), javaAgents, 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 +530,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();
@@ -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,9 +24,9 @@ import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.util.Pair;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.IOUtils;
import java.io.File;
import java.io.IOException;
@@ -36,11 +36,10 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
@@ -58,34 +57,6 @@ public class Theme {
Color.web("#B71C1C") // red
};
private static Charset cssCharset;
private static Charset getCssCharset() {
if (cssCharset != null)
return cssCharset;
Charset defaultCharset = Charset.defaultCharset();
if (defaultCharset != StandardCharsets.UTF_8) {
// https://bugs.openjdk.org/browse/JDK-8279328
// For JavaFX 17 or earlier, native encoding should be used
String jfxVersion = System.getProperty("javafx.version");
if (jfxVersion != null) {
Matcher matcher = Pattern.compile("^(?<version>[0-9]+)").matcher(jfxVersion);
if (matcher.find()) {
int v = Lang.parseInt(matcher.group(), -1);
if (v >= 18) {
cssCharset = StandardCharsets.UTF_8;
}
}
}
}
if (cssCharset == null)
cssCharset = defaultCharset;
return cssCharset;
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private static Optional<Font> font;
@@ -145,6 +116,53 @@ public class Theme {
return isLight() ? Color.BLACK : Color.WHITE;
}
private static String rgba(Color color, double opacity) {
return String.format("rgba(%d, %d, %d, %.1f)",
(int) Math.ceil(color.getRed() * 256),
(int) Math.ceil(color.getGreen() * 256),
(int) Math.ceil(color.getBlue() * 256),
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";
@@ -152,41 +170,60 @@ 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();
fontStyle = parseFontStyle(font.get().getStyle());
}
}
if (fontFamily != null || !this.color.equalsIgnoreCase(BLUE.color)) {
Color textFill = getForegroundColor();
String fontCss = "";
if (fontFamily != null) {
fontCss = "-fx-font-family: \"" + fontFamily + "\";";
if (fontStyle != null && !fontStyle.isEmpty())
fontCss += " -fx-font-style: \"" + fontStyle + "\";";
StringBuilder themeBuilder = new StringBuilder(512);
themeBuilder.append(".root {")
.append("-fx-base-color:").append(color).append(';')
.append("-fx-base-darker-color: derive(-fx-base-color, -10%);")
.append("-fx-base-check-color: derive(-fx-base-color, 30%);")
.append("-fx-rippler-color:").append(rgba(paint, 0.3)).append(';')
.append("-fx-base-rippler-color: derive(").append(rgba(paint, 0.3)).append(", 100%);")
.append("-fx-base-disabled-text-fill:").append(rgba(textFill, 0.7)).append(";")
.append("-fx-base-text-fill:").append(getColorDisplayName(getForegroundColor())).append(";")
.append("-theme-thumb:").append(rgba(paint, 0.7)).append(";");
if (fontFamily == null)
// https://github.com/HMCL-dev/HMCL/pull/3423
themeBuilder.append("-fx-font-family: -fx-base-font-family;");
else
themeBuilder.append("-fx-font-family:\"").append(fontFamily).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(";");
}
try {
File temp = File.createTempFile("hmcl", ".css");
String themeText = IOUtils.readFullyAsString(Theme.class.getResourceAsStream("/assets/css/custom.css"))
.replace("%base-color%", color)
.replace("%base-red%", Integer.toString((int) Math.ceil(paint.getRed() * 256)))
.replace("%base-green%", Integer.toString((int) Math.ceil(paint.getGreen() * 256)))
.replace("%base-blue%", Integer.toString((int) Math.ceil(paint.getBlue() * 256)))
.replace("%base-rippler-color%", String.format("rgba(%d, %d, %d, 0.3)", (int) Math.ceil(paint.getRed() * 256), (int) Math.ceil(paint.getGreen() * 256), (int) Math.ceil(paint.getBlue() * 256)))
.replace("%disabled-font-color%", String.format("rgba(%d, %d, %d, 0.7)", (int) Math.ceil(textFill.getRed() * 256), (int) Math.ceil(textFill.getGreen() * 256), (int) Math.ceil(textFill.getBlue() * 256)))
.replace("%font-color%", getColorDisplayName(getForegroundColor()))
.replace("%font%", fontCss);
FileUtils.writeText(temp, themeText, getCssCharset());
temp.deleteOnExit();
css = temp.toURI().toString();
} catch (IOException | NullPointerException e) {
LOG.error("Unable to create theme stylesheet. Fallback to blue theme.", e);
}
themeBuilder.append('}');
if (FXUtils.JAVAFX_MAJOR_VERSION >= 17)
// JavaFX 17+ support loading stylesheets from data URIs
// https://bugs.openjdk.org/browse/JDK-8267554
css = "data:text/css;charset=UTF-8;base64," + Base64.getEncoder().encodeToString(themeBuilder.toString().getBytes(StandardCharsets.UTF_8));
else
try {
File temp = File.createTempFile("hmcl", ".css");
// For JavaFX 17 or earlier, CssParser uses the default charset
// https://bugs.openjdk.org/browse/JDK-8279328
FileUtils.writeText(temp, themeBuilder.toString(), Charset.defaultCharset());
temp.deleteOnExit();
css = temp.toURI().toString();
} catch (IOException | NullPointerException e) {
LOG.error("Unable to create theme stylesheet. Fallback to blue theme.", e);
}
}
return new String[]{css, "/assets/css/root.css"};
@@ -235,7 +272,7 @@ public class Theme {
}
public static String getColorDisplayName(Color c) {
return c != null ? String.format("#%02x%02x%02x", Math.round(c.getRed() * 255.0D), Math.round(c.getGreen() * 255.0D), Math.round(c.getBlue() * 255.0D)).toUpperCase(Locale.ROOT) : null;
return c != null ? String.format("#%02X%02X%02X", Math.round(c.getRed() * 255.0D), Math.round(c.getGreen() * 255.0D), Math.round(c.getBlue() * 255.0D)) : null;
}
private static ObjectBinding<Color> FOREGROUND_FILL;
@@ -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> {
@@ -18,6 +18,7 @@
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;
@@ -26,10 +27,14 @@ import javafx.beans.WeakInvalidationListener;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.Property;
import javafx.beans.value.*;
import javafx.event.Event;
import javafx.event.EventDispatcher;
import javafx.event.EventType;
import javafx.geometry.Insets;
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,10 +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.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;
@@ -67,15 +72,14 @@ 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.lang.reflect.Constructor;
import java.lang.reflect.Method;
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;
@@ -85,6 +89,9 @@ 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;
import static org.jackhuang.hmcl.util.Lang.thread;
@@ -96,8 +103,26 @@ public final class FXUtils {
private FXUtils() {
}
public static final int JAVAFX_MAJOR_VERSION;
static {
String jfxVersion = System.getProperty("javafx.version");
int majorVersion = -1;
if (jfxVersion != null) {
Matcher matcher = Pattern.compile("^(?<version>[0-9]+)").matcher(jfxVersion);
if (matcher.find()) {
majorVersion = Lang.parseInt(matcher.group(), -1);
}
}
JAVAFX_MAJOR_VERSION = majorVersion;
}
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<>();
@@ -213,6 +238,21 @@ public final class FXUtils {
});
}
@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 <K, T> void setupCellValueFactory(JFXTreeTableColumn<K, T> column, Function<K, ObservableValue<T>> mapper) {
column.setCellValueFactory(param -> {
if (column.validateValue(param))
@@ -298,8 +338,12 @@ public final class FXUtils {
ScrollUtils.addSmoothScrolling(scrollPane);
}
private static final Duration TOOLTIP_FAST_SHOW_DELAY = Duration.millis(50);
private static final Duration TOOLTIP_SLOW_SHOW_DELAY = Duration.millis(500);
private static final Duration TOOLTIP_SHOW_DURATION = Duration.millis(5000);
public static void installFastTooltip(Node node, Tooltip tooltip) {
installTooltip(node, 50, 5000, 0, tooltip);
runInFX(() -> TooltipInstaller.INSTALLER.installTooltip(node, TOOLTIP_FAST_SHOW_DELAY, TOOLTIP_SHOW_DURATION, Duration.ZERO, tooltip));
}
public static void installFastTooltip(Node node, String tooltip) {
@@ -307,39 +351,13 @@ public final class FXUtils {
}
public static void installSlowTooltip(Node node, Tooltip tooltip) {
installTooltip(node, 500, 5000, 0, tooltip);
runInFX(() -> TooltipInstaller.INSTALLER.installTooltip(node, TOOLTIP_SLOW_SHOW_DELAY, TOOLTIP_SHOW_DURATION, Duration.ZERO, tooltip));
}
public static void installSlowTooltip(Node node, String tooltip) {
installSlowTooltip(node, new Tooltip(tooltip));
}
public static void installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) {
runInFX(() -> {
try {
// Java 8
Class<?> behaviorClass = Class.forName("javafx.scene.control.Tooltip$TooltipBehavior");
Constructor<?> behaviorConstructor = behaviorClass.getDeclaredConstructor(Duration.class, Duration.class, Duration.class, boolean.class);
behaviorConstructor.setAccessible(true);
Object behavior = behaviorConstructor.newInstance(new Duration(openDelay), new Duration(visibleDelay), new Duration(closeDelay), false);
Method installMethod = behaviorClass.getDeclaredMethod("install", Node.class, Tooltip.class);
installMethod.setAccessible(true);
installMethod.invoke(behavior, node, tooltip);
} catch (ReflectiveOperationException e) {
try {
// Java 9
Tooltip.class.getMethod("setShowDelay", Duration.class).invoke(tooltip, new Duration(openDelay));
Tooltip.class.getMethod("setShowDuration", Duration.class).invoke(tooltip, new Duration(visibleDelay));
Tooltip.class.getMethod("setHideDelay", Duration.class).invoke(tooltip, new Duration(closeDelay));
} catch (ReflectiveOperationException e2) {
e.addSuppressed(e2);
LOG.error("Cannot install tooltip", e);
}
Tooltip.install(node, tooltip);
}
});
}
public static void playAnimation(Node node, String animationKey, Timeline timeline) {
animationKey = "FXUTILS.ANIMATION." + animationKey;
Object oldTimeline = node.getProperties().get(animationKey);
@@ -522,10 +540,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) {
@@ -537,68 +557,66 @@ 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);
}
@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();
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 (!Objects.equals(newValue, property.getValue()))
property.setValue(newValue);
} else {
// Rollback to old value
invalidated(null);
focusedListener = (observable, oldFocused, newFocused) -> {
if (oldFocused && !newFocused) {
if (textField.validate()) {
uppdateProperty();
} else {
// Rollback to old value
updateTextField();
}
}
};
sceneListener = (observable, oldScene, newScene) -> {
if (oldScene != null && newScene == null) {
// Component is being removed from scene
if (textField.validate()) {
uppdateProperty();
}
}
};
propertyListener = observable -> {
updateTextField();
};
}
public void uppdateProperty() {
String newText = textField.getText();
@SuppressWarnings("unchecked")
T newValue = converter == null ? (T) newText : converter.fromString(newText);
if (!Objects.equals(newValue, property.getValue())) {
property.setValue(newValue);
}
}
@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));
}
}
@Override
public int hashCode() {
return hashCode;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof TextFieldBindingListener))
return false;
TextFieldBindingListener<?> other = (TextFieldBindingListener<?>) obj;
return this.hashCode == other.hashCode
&& this.textFieldRef.get() == other.textFieldRef.get()
&& this.propertyRef.get() == other.propertyRef.get();
public void updateTextField() {
T value = property.getValue();
textField.setText(converter == null ? (String) value : converter.toString(value));
}
}
@@ -720,6 +738,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.
*
@@ -1041,4 +1103,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));
}
}
@@ -65,7 +65,6 @@ import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.Pair.pair;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
@@ -387,13 +386,13 @@ public class GameCrashWindow extends Stage {
gameDir.getStyleClass().setAll("two-line-item-second-large");
gameDir.setTitle(i18n("game.directory"));
gameDir.setSubtitle(launchOptions.getGameDir().getAbsolutePath());
runInFX(() -> FXUtils.installFastTooltip(gameDir, i18n("game.directory")));
FXUtils.installFastTooltip(gameDir, i18n("game.directory"));
TwoLineListItem javaDir = new TwoLineListItem();
javaDir.getStyleClass().setAll("two-line-item-second-large");
javaDir.setTitle(i18n("settings.game.java_directory"));
javaDir.setSubtitle(launchOptions.getJava().getBinary().toAbsolutePath().toString());
runInFX(() -> FXUtils.installFastTooltip(javaDir, i18n("settings.game.java_directory")));
FXUtils.installFastTooltip(javaDir, i18n("settings.game.java_directory"));
Label reasonTitle = new Label(i18n("game.crash.reason"));
reasonTitle.getStyleClass().add("two-line-item-second-large-title");
@@ -424,7 +423,7 @@ public class GameCrashWindow extends Stage {
JFXButton helpButton = FXUtils.newRaisedButton(i18n("help"));
helpButton.setOnAction(e -> FXUtils.openLink("https://docs.hmcl.net/help.html"));
runInFX(() -> FXUtils.installFastTooltip(helpButton, i18n("logwindow.help")));
FXUtils.installFastTooltip(helpButton, i18n("logwindow.help"));
toolBar.setPadding(new Insets(8));
@@ -32,7 +32,7 @@ public enum SVG {
CANCEL("M12 2C17.5 2 22 6.5 22 12S17.5 22 12 22 2 17.5 2 12 6.5 2 12 2M12 4C10.1 4 8.4 4.6 7.1 5.7L18.3 16.9C19.3 15.5 20 13.8 20 12C20 7.6 16.4 4 12 4M16.9 18.3L5.7 7.1C4.6 8.4 4 10.1 4 12C4 16.4 7.6 20 12 20C13.9 20 15.6 19.4 16.9 18.3Z"),
CHAT("M20,2A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H6L2,22V4C2,2.89 2.9,2 4,2H20M4,4V17.17L5.17,16H20V4H4M6,7H18V9H6V7M6,11H15V13H6V11Z"),
CLOSE("M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"),
COPY("M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"),
COPY("M15 16H7V6h8m.4-2H6.6A1.6 1.6 90 005 5.6V16.4a1.6 1.6 90 001.6 1.6h8.8A1.6 1.6 90 0017 16.4V5.6A1.6 1.6 90 0015.4 4M13 1H3.6A1.6 1.6 90 002 2.6V13h2V3H13V1Z"),
DOTS_VERTICAL("M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z"),
DOTS_HORIZONTAL("M16,12A2,2 0 0,1 18,10A2,2 0 0,1 20,12A2,2 0 0,1 18,14A2,2 0 0,1 16,12M10,12A2,2 0 0,1 12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12M4,12A2,2 0 0,1 6,10A2,2 0 0,1 8,12A2,2 0 0,1 6,14A2,2 0 0,1 4,12Z"),
DELETE("M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z"),
@@ -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"),
@@ -0,0 +1,123 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui;
import javafx.scene.Node;
import javafx.scene.control.Tooltip;
import javafx.util.Duration;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
/**
* @author Glavo
*/
public class TooltipInstaller {
public static final TooltipInstaller INSTALLER;
static {
TooltipInstaller installer = null;
try {
installer = new NewInstaller();
} catch (Throwable e) {
try {
installer = new OldInstaller();
} catch (Throwable e2) {
e2.addSuppressed(e);
LOG.warning("Failed to initialize TooltipInstaller", e2);
}
}
INSTALLER = installer != null ? installer : new TooltipInstaller();
}
TooltipInstaller() {
}
public void installTooltip(Node node, Duration showDelay, Duration showDuration, Duration hideDelay, Tooltip tooltip) {
Tooltip.install(node, tooltip);
}
// For Java 8
private static final class OldInstaller extends TooltipInstaller {
private static final Constructor<?> createTooltipBehavior;
private static final Method installTooltipBehavior;
static {
try {
Class<?> behaviorClass = Class.forName("javafx.scene.control.Tooltip$TooltipBehavior");
createTooltipBehavior = behaviorClass.getDeclaredConstructor(Duration.class, Duration.class, Duration.class, boolean.class);
createTooltipBehavior.setAccessible(true);
installTooltipBehavior = behaviorClass.getDeclaredMethod("install", Node.class, Tooltip.class);
installTooltipBehavior.setAccessible(true);
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
@Override
public void installTooltip(Node node, Duration showDelay, Duration showDuration, Duration hideDelay, Tooltip tooltip) {
try {
Object behavior = createTooltipBehavior.newInstance(showDelay, showDuration, hideDelay, false);
installTooltipBehavior.invoke(behavior, node, tooltip);
} catch (ReflectiveOperationException e) {
LOG.warning("Failed to set tooltip show delay", e);
Tooltip.install(node, tooltip);
}
}
}
// For Java 9+
private static final class NewInstaller extends TooltipInstaller {
private static final MethodHandle setTooltipShowDelay;
private static final MethodHandle setTooltipShowDuration;
private static final MethodHandle setTooltipHideDelay;
static {
try {
MethodHandles.Lookup lookup = MethodHandles.publicLookup();
MethodType methodType = MethodType.methodType(void.class, Duration.class);
setTooltipShowDelay = lookup.findVirtual(Tooltip.class, "setShowDelay", methodType);
setTooltipShowDuration = lookup.findVirtual(Tooltip.class, "setShowDuration", methodType);
setTooltipHideDelay = lookup.findVirtual(Tooltip.class, "setHideDelay", methodType);
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
@Override
public void installTooltip(Node node, Duration showDelay, Duration showDuration, Duration hideDelay, Tooltip tooltip) {
try {
setTooltipShowDelay.invokeExact(tooltip, showDelay);
setTooltipShowDuration.invokeExact(tooltip, showDuration);
setTooltipHideDelay.invokeExact(tooltip, hideDelay);
} catch (Throwable e) {
LOG.warning("Failed to set tooltip show delay", e);
}
Tooltip.install(node, tooltip);
}
}
}
@@ -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());
@@ -42,10 +42,9 @@ import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
import org.jackhuang.hmcl.util.javafx.BindingMapping;
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class AccountListItemSkin extends SkinBase<AccountListItem> {
public final class AccountListItemSkin extends SkinBase<AccountListItem> {
public AccountListItemSkin(AccountListItem skinnable) {
super(skinnable);
@@ -118,10 +117,10 @@ public class AccountListItemSkin extends SkinBase<AccountListItem> {
btnMove.getStyleClass().add("toggle-icon4");
if (skinnable.getAccount().isPortable()) {
btnMove.setGraphic(SVG.EARTH.createIcon(Theme.blackFill(), -1, -1));
runInFX(() -> FXUtils.installFastTooltip(btnMove, i18n("account.move_to_global")));
FXUtils.installFastTooltip(btnMove, i18n("account.move_to_global"));
} else {
btnMove.setGraphic(SVG.EXPORT.createIcon(Theme.blackFill(), -1, -1));
runInFX(() -> FXUtils.installFastTooltip(btnMove, i18n("account.move_to_portable")));
FXUtils.installFastTooltip(btnMove, i18n("account.move_to_portable"));
}
spinnerMove.setContent(btnMove);
right.getChildren().add(spinnerMove);
@@ -143,7 +142,7 @@ public class AccountListItemSkin extends SkinBase<AccountListItem> {
});
btnRefresh.getStyleClass().add("toggle-icon4");
btnRefresh.setGraphic(SVG.REFRESH.createIcon(Theme.blackFill(), -1, -1));
runInFX(() -> FXUtils.installFastTooltip(btnRefresh, i18n("button.refresh")));
FXUtils.installFastTooltip(btnRefresh, i18n("button.refresh"));
spinnerRefresh.setContent(btnRefresh);
right.getChildren().add(spinnerRefresh);
@@ -160,7 +159,7 @@ public class AccountListItemSkin extends SkinBase<AccountListItem> {
});
btnUpload.getStyleClass().add("toggle-icon4");
btnUpload.setGraphic(SVG.HANGER.createIcon(Theme.blackFill(), -1, -1));
runInFX(() -> FXUtils.installFastTooltip(btnUpload, i18n("account.skin.upload")));
FXUtils.installFastTooltip(btnUpload, i18n("account.skin.upload"));
spinnerUpload.managedProperty().bind(spinnerUpload.visibleProperty());
spinnerUpload.visibleProperty().bind(skinnable.canUploadSkin());
spinnerUpload.setContent(btnUpload);
@@ -170,9 +169,10 @@ public class AccountListItemSkin extends SkinBase<AccountListItem> {
JFXButton btnCopyUUID = new JFXButton();
SpinnerPane spinnerCopyUUID = new SpinnerPane();
spinnerCopyUUID.getStyleClass().add("small-spinner-pane");
btnUpload.getStyleClass().add("toggle-icon4");
btnCopyUUID.setOnAction(e -> FXUtils.copyText(skinnable.getAccount().getUUID().toString()));
btnCopyUUID.setGraphic(SVG.COPY.createIcon(Theme.blackFill(), -1, -1));
runInFX(() -> FXUtils.installFastTooltip(btnCopyUUID, i18n("account.copy_uuid")));
FXUtils.installFastTooltip(btnCopyUUID, i18n("account.copy_uuid"));
spinnerCopyUUID.setContent(btnCopyUUID);
right.getChildren().add(spinnerCopyUUID);
@@ -180,8 +180,8 @@ public class AccountListItemSkin extends SkinBase<AccountListItem> {
btnRemove.setOnAction(e -> Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), skinnable::remove, null));
btnRemove.getStyleClass().add("toggle-icon4");
BorderPane.setAlignment(btnRemove, Pos.CENTER);
btnRemove.setGraphic(SVG.DELETE.createIcon(Theme.blackFill(), -1, -1));
runInFX(() -> FXUtils.installFastTooltip(btnRemove, i18n("button.delete")));
btnRemove.setGraphic(SVG.DELETE_OUTLINE.createIcon(Theme.blackFill(), -1, -1));
FXUtils.installFastTooltip(btnRemove, i18n("button.delete"));
right.getChildren().add(btnRemove);
root.setRight(right);
@@ -147,7 +147,7 @@ public final class AddAuthlibInjectorServerPane extends TransitionPane implement
confirmServerPane.setActions(prevButton, cancelButton, finishButton);
}
this.setContent(addServerPane, ContainerAnimations.NONE.getAnimationProducer());
this.setContent(addServerPane, ContainerAnimations.NONE);
lblCreationWarning.maxWidthProperty().bind(((FlowPane) lblCreationWarning.getParent()).widthProperty());
btnAddNext.disableProperty().bind(txtServerUrl.textProperty().isEmpty());
@@ -198,7 +198,7 @@ public final class AddAuthlibInjectorServerPane extends TransitionPane implement
lblServerWarning.setVisible("http".equals(NetworkUtils.toURL(serverBeingAdded.getUrl()).getProtocol()));
this.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT.getAnimationProducer());
this.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT);
} else {
LOG.warning("Failed to resolve auth server: " + url, exception);
lblCreationWarning.setText(resolveFetchExceptionMessage(exception));
@@ -208,7 +208,7 @@ public final class AddAuthlibInjectorServerPane extends TransitionPane implement
}
private void onAddPrev() {
this.setContent(addServerPane, ContainerAnimations.SWIPE_RIGHT.getAnimationProducer());
this.setContent(addServerPane, ContainerAnimations.SWIPE_RIGHT);
}
private void onAddFinish() {
@@ -317,7 +317,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
JFXHyperlink deauthorizeLink = new JFXHyperlink(i18n("account.methods.microsoft.deauthorize"));
deauthorizeLink.setExternalLink("https://account.live.com/consent/Edit?client_id=000000004C794E0A");
JFXHyperlink forgotpasswordLink = new JFXHyperlink(i18n("account.methods.forgot_password"));
forgotpasswordLink.setExternalLink("https://www.minecraft.net/password/forgot");
forgotpasswordLink.setExternalLink("https://account.live.com/ResetPassword.aspx");
JFXHyperlink createProfileLink = new JFXHyperlink(i18n("account.methods.microsoft.makegameidsettings"));
createProfileLink.setExternalLink("https://www.minecraft.net/msaprofile/mygames/editprofile");
box.getChildren().setAll(profileLink, birthLink, purchaseLink, deauthorizeLink, forgotpasswordLink, createProfileLink);
@@ -510,7 +510,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
if (factory instanceof OfflineAccountFactory) {
txtUsername.setPromptText(i18n("account.methods.offline.name.special_characters"));
runInFX(() -> FXUtils.installFastTooltip(txtUsername, i18n("account.methods.offline.name.special_characters")));
FXUtils.installFastTooltip(txtUsername, i18n("account.methods.offline.name.special_characters"));
JFXHyperlink purchaseLink = new JFXHyperlink(i18n("account.methods.microsoft.purchase"));
purchaseLink.setExternalLink(YggdrasilService.PURCHASE_URL);
@@ -28,9 +28,9 @@ import javafx.scene.control.Label;
import javafx.scene.input.DragEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.*;
import moe.mickey.minecraft.skin.fx.SkinCanvas;
import moe.mickey.minecraft.skin.fx.animation.SkinAniRunning;
import moe.mickey.minecraft.skin.fx.animation.SkinAniWavingArms;
import org.jackhuang.hmcl.ui.skin.SkinCanvas;
import org.jackhuang.hmcl.ui.skin.animation.SkinAniRunning;
import org.jackhuang.hmcl.ui.skin.animation.SkinAniWavingArms;
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
import org.jackhuang.hmcl.auth.offline.Skin;
import org.jackhuang.hmcl.auth.yggdrasil.TextureModel;
@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -27,165 +27,225 @@ import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
public enum ContainerAnimations {
NONE(c -> {
c.getPreviousNode().setTranslateX(0);
c.getPreviousNode().setTranslateY(0);
c.getPreviousNode().setScaleX(1);
c.getPreviousNode().setScaleY(1);
c.getPreviousNode().setOpacity(1);
c.getCurrentNode().setTranslateX(0);
c.getCurrentNode().setTranslateY(0);
c.getCurrentNode().setScaleX(1);
c.getCurrentNode().setScaleY(1);
c.getCurrentNode().setOpacity(1);
}, c -> Collections.emptyList()),
public enum ContainerAnimations implements AnimationProducer {
NONE {
@Override
public void init(AnimationHandler c) {
c.getPreviousNode().setTranslateX(0);
c.getPreviousNode().setTranslateY(0);
c.getPreviousNode().setScaleX(1);
c.getPreviousNode().setScaleY(1);
c.getPreviousNode().setOpacity(1);
c.getCurrentNode().setTranslateX(0);
c.getCurrentNode().setTranslateY(0);
c.getCurrentNode().setScaleX(1);
c.getCurrentNode().setScaleY(1);
c.getCurrentNode().setOpacity(1);
}
@Override
public List<KeyFrame> animate(AnimationHandler c) {
return Collections.emptyList();
}
},
/**
* A fade between the old and new view
*/
FADE(c -> {
c.getPreviousNode().setTranslateX(0);
c.getPreviousNode().setTranslateY(0);
c.getPreviousNode().setScaleX(1);
c.getPreviousNode().setScaleY(1);
c.getPreviousNode().setOpacity(1);
c.getCurrentNode().setTranslateX(0);
c.getCurrentNode().setTranslateY(0);
c.getCurrentNode().setScaleX(1);
c.getCurrentNode().setScaleY(1);
c.getCurrentNode().setOpacity(0);
}, c ->
Arrays.asList(new KeyFrame(Duration.ZERO,
FADE {
@Override
public void init(AnimationHandler c) {
c.getPreviousNode().setTranslateX(0);
c.getPreviousNode().setTranslateY(0);
c.getPreviousNode().setScaleX(1);
c.getPreviousNode().setScaleY(1);
c.getPreviousNode().setOpacity(1);
c.getCurrentNode().setTranslateX(0);
c.getCurrentNode().setTranslateY(0);
c.getCurrentNode().setScaleX(1);
c.getCurrentNode().setScaleY(1);
c.getCurrentNode().setOpacity(0);
}
@Override
public List<KeyFrame> animate(AnimationHandler c) {
return Arrays.asList(new KeyFrame(Duration.ZERO,
new KeyValue(c.getPreviousNode().opacityProperty(), 1, Interpolator.EASE_BOTH),
new KeyValue(c.getCurrentNode().opacityProperty(), 0, Interpolator.EASE_BOTH)),
new KeyFrame(c.getDuration(),
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH),
new KeyValue(c.getCurrentNode().opacityProperty(), 1, Interpolator.EASE_BOTH)))),
new KeyValue(c.getCurrentNode().opacityProperty(), 1, Interpolator.EASE_BOTH)));
}
},
/**
* A fade between the old and new view
*/
FADE_IN(c -> {
c.getCurrentNode().setTranslateX(0);
c.getCurrentNode().setTranslateY(0);
c.getCurrentNode().setScaleX(1);
c.getCurrentNode().setScaleY(1);
c.getCurrentNode().setOpacity(0);
}, c ->
Arrays.asList(new KeyFrame(Duration.ZERO,
FADE_IN {
@Override
public void init(AnimationHandler c) {
c.getCurrentNode().setTranslateX(0);
c.getCurrentNode().setTranslateY(0);
c.getCurrentNode().setScaleX(1);
c.getCurrentNode().setScaleY(1);
c.getCurrentNode().setOpacity(0);
}
@Override
public List<KeyFrame> animate(AnimationHandler c) {
return Arrays.asList(new KeyFrame(Duration.ZERO,
new KeyValue(c.getCurrentNode().opacityProperty(), 0, FXUtils.SINE)),
new KeyFrame(c.getDuration(),
new KeyValue(c.getCurrentNode().opacityProperty(), 1, FXUtils.SINE)))),
new KeyValue(c.getCurrentNode().opacityProperty(), 1, FXUtils.SINE)));
}
},
/**
* A fade between the old and new view
*/
FADE_OUT(c -> {
c.getCurrentNode().setTranslateX(0);
c.getCurrentNode().setTranslateY(0);
c.getCurrentNode().setScaleX(1);
c.getCurrentNode().setScaleY(1);
c.getCurrentNode().setOpacity(1);
}, c ->
Arrays.asList(new KeyFrame(Duration.ZERO,
FADE_OUT {
@Override
public void init(AnimationHandler c) {
c.getCurrentNode().setTranslateX(0);
c.getCurrentNode().setTranslateY(0);
c.getCurrentNode().setScaleX(1);
c.getCurrentNode().setScaleY(1);
c.getCurrentNode().setOpacity(1);
}
@Override
public List<KeyFrame> animate(AnimationHandler c) {
return Arrays.asList(new KeyFrame(Duration.ZERO,
new KeyValue(c.getCurrentNode().opacityProperty(), 1, FXUtils.SINE)),
new KeyFrame(c.getDuration(),
new KeyValue(c.getCurrentNode().opacityProperty(), 0, FXUtils.SINE)))),
new KeyValue(c.getCurrentNode().opacityProperty(), 0, FXUtils.SINE)));
}
},
/**
* A zoom effect
*/
ZOOM_IN(c -> {
c.getPreviousNode().setTranslateX(0);
c.getPreviousNode().setTranslateY(0);
c.getPreviousNode().setScaleX(1);
c.getPreviousNode().setScaleY(1);
c.getPreviousNode().setOpacity(1);
c.getCurrentNode().setTranslateX(0);
c.getCurrentNode().setTranslateY(0);
}, c ->
Arrays.asList(new KeyFrame(Duration.ZERO,
ZOOM_IN {
@Override
public void init(AnimationHandler c) {
c.getPreviousNode().setTranslateX(0);
c.getPreviousNode().setTranslateY(0);
c.getPreviousNode().setScaleX(1);
c.getPreviousNode().setScaleY(1);
c.getPreviousNode().setOpacity(1);
c.getCurrentNode().setTranslateX(0);
c.getCurrentNode().setTranslateY(0);
}
@Override
public List<KeyFrame> animate(AnimationHandler c) {
return Arrays.asList(new KeyFrame(Duration.ZERO,
new KeyValue(c.getPreviousNode().scaleXProperty(), 1, Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().scaleYProperty(), 1, Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().opacityProperty(), 1, Interpolator.EASE_BOTH)),
new KeyFrame(c.getDuration(),
new KeyValue(c.getPreviousNode().scaleXProperty(), 4, Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().scaleYProperty(), 4, Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH)))),
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH)));
}
},
/**
* A zoom effect
*/
ZOOM_OUT(c -> {
c.getPreviousNode().setTranslateX(0);
c.getPreviousNode().setTranslateY(0);
c.getPreviousNode().setScaleX(1);
c.getPreviousNode().setScaleY(1);
c.getPreviousNode().setOpacity(1);
c.getCurrentNode().setTranslateX(0);
c.getCurrentNode().setTranslateY(0);
}, c ->
(Arrays.asList(new KeyFrame(Duration.ZERO,
ZOOM_OUT {
@Override
public void init(AnimationHandler c) {
c.getPreviousNode().setTranslateX(0);
c.getPreviousNode().setTranslateY(0);
c.getPreviousNode().setScaleX(1);
c.getPreviousNode().setScaleY(1);
c.getPreviousNode().setOpacity(1);
c.getCurrentNode().setTranslateX(0);
c.getCurrentNode().setTranslateY(0);
}
@Override
public List<KeyFrame> animate(AnimationHandler c) {
return Arrays.asList(new KeyFrame(Duration.ZERO,
new KeyValue(c.getPreviousNode().scaleXProperty(), 1, Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().scaleYProperty(), 1, Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().opacityProperty(), 1, Interpolator.EASE_BOTH)),
new KeyFrame(c.getDuration(),
new KeyValue(c.getPreviousNode().scaleXProperty(), 0, Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().scaleYProperty(), 0, Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH))))),
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH)));
}
},
/**
* A swipe effect
*/
SWIPE_LEFT(c -> {
c.getPreviousNode().setScaleX(1);
c.getPreviousNode().setScaleY(1);
c.getPreviousNode().setOpacity(0);
c.getPreviousNode().setTranslateX(0);
c.getCurrentNode().setScaleX(1);
c.getCurrentNode().setScaleY(1);
c.getCurrentNode().setOpacity(1);
c.getCurrentNode().setTranslateX(c.getCurrentRoot().getWidth());
}, c ->
Arrays.asList(new KeyFrame(Duration.ZERO,
SWIPE_LEFT {
@Override
public void init(AnimationHandler c) {
c.getPreviousNode().setScaleX(1);
c.getPreviousNode().setScaleY(1);
c.getPreviousNode().setOpacity(0);
c.getPreviousNode().setTranslateX(0);
c.getCurrentNode().setScaleX(1);
c.getCurrentNode().setScaleY(1);
c.getCurrentNode().setOpacity(1);
c.getCurrentNode().setTranslateX(c.getCurrentRoot().getWidth());
}
@Override
public List<KeyFrame> animate(AnimationHandler c) {
return Arrays.asList(new KeyFrame(Duration.ZERO,
new KeyValue(c.getCurrentNode().translateXProperty(), c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().translateXProperty(), 0, Interpolator.EASE_BOTH)),
new KeyFrame(c.getDuration(),
new KeyValue(c.getCurrentNode().translateXProperty(), 0, Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().translateXProperty(), -c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH)))),
new KeyValue(c.getPreviousNode().translateXProperty(), -c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH)));
}
},
/**
* A swipe effect
*/
SWIPE_RIGHT(c -> {
c.getPreviousNode().setScaleX(1);
c.getPreviousNode().setScaleY(1);
c.getPreviousNode().setOpacity(0);
c.getPreviousNode().setTranslateX(0);
c.getCurrentNode().setScaleX(1);
c.getCurrentNode().setScaleY(1);
c.getCurrentNode().setOpacity(1);
c.getCurrentNode().setTranslateX(-c.getCurrentRoot().getWidth());
}, c ->
Arrays.asList(new KeyFrame(Duration.ZERO,
SWIPE_RIGHT {
@Override
public void init(AnimationHandler c) {
c.getPreviousNode().setScaleX(1);
c.getPreviousNode().setScaleY(1);
c.getPreviousNode().setOpacity(0);
c.getPreviousNode().setTranslateX(0);
c.getCurrentNode().setScaleX(1);
c.getCurrentNode().setScaleY(1);
c.getCurrentNode().setOpacity(1);
c.getCurrentNode().setTranslateX(-c.getCurrentRoot().getWidth());
}
@Override
public List<KeyFrame> animate(AnimationHandler c) {
return Arrays.asList(new KeyFrame(Duration.ZERO,
new KeyValue(c.getCurrentNode().translateXProperty(), -c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().translateXProperty(), 0, Interpolator.EASE_BOTH)),
new KeyFrame(c.getDuration(),
new KeyValue(c.getCurrentNode().translateXProperty(), 0, Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().translateXProperty(), c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH)))),
new KeyValue(c.getPreviousNode().translateXProperty(), c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH)));
}
},
SWIPE_LEFT_FADE_SHORT(c -> {
c.getPreviousNode().setScaleX(1);
c.getPreviousNode().setScaleY(1);
c.getPreviousNode().setOpacity(0);
c.getPreviousNode().setTranslateX(0);
c.getCurrentNode().setScaleX(1);
c.getCurrentNode().setScaleY(1);
c.getCurrentNode().setOpacity(1);
c.getCurrentNode().setTranslateX(c.getCurrentRoot().getWidth());
}, c ->
Arrays.asList(new KeyFrame(Duration.ZERO,
SWIPE_LEFT_FADE_SHORT {
@Override
public void init(AnimationHandler c) {
c.getPreviousNode().setScaleX(1);
c.getPreviousNode().setScaleY(1);
c.getPreviousNode().setOpacity(0);
c.getPreviousNode().setTranslateX(0);
c.getCurrentNode().setScaleX(1);
c.getCurrentNode().setScaleY(1);
c.getCurrentNode().setOpacity(1);
c.getCurrentNode().setTranslateX(c.getCurrentRoot().getWidth());
}
@Override
public List<KeyFrame> animate(AnimationHandler c) {
return Arrays.asList(new KeyFrame(Duration.ZERO,
new KeyValue(c.getCurrentNode().translateXProperty(), 50, Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().translateXProperty(), 0, Interpolator.EASE_BOTH),
new KeyValue(c.getCurrentNode().opacityProperty(), 0, Interpolator.EASE_BOTH),
@@ -194,19 +254,26 @@ public enum ContainerAnimations {
new KeyValue(c.getCurrentNode().translateXProperty(), 0, Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().translateXProperty(), -50, Interpolator.EASE_BOTH),
new KeyValue(c.getCurrentNode().opacityProperty(), 1, Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH)))),
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH)));
}
},
SWIPE_RIGHT_FADE_SHORT(c -> {
c.getPreviousNode().setScaleX(1);
c.getPreviousNode().setScaleY(1);
c.getPreviousNode().setOpacity(0);
c.getPreviousNode().setTranslateX(0);
c.getCurrentNode().setScaleX(1);
c.getCurrentNode().setScaleY(1);
c.getCurrentNode().setOpacity(1);
c.getCurrentNode().setTranslateX(c.getCurrentRoot().getWidth());
}, c ->
Arrays.asList(new KeyFrame(Duration.ZERO,
SWIPE_RIGHT_FADE_SHORT {
@Override
public void init(AnimationHandler c) {
c.getPreviousNode().setScaleX(1);
c.getPreviousNode().setScaleY(1);
c.getPreviousNode().setOpacity(0);
c.getPreviousNode().setTranslateX(0);
c.getCurrentNode().setScaleX(1);
c.getCurrentNode().setScaleY(1);
c.getCurrentNode().setOpacity(1);
c.getCurrentNode().setTranslateX(c.getCurrentRoot().getWidth());
}
@Override
public List<KeyFrame> animate(AnimationHandler c) {
return Arrays.asList(new KeyFrame(Duration.ZERO,
new KeyValue(c.getCurrentNode().translateXProperty(), -50, Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().translateXProperty(), 0, Interpolator.EASE_BOTH),
new KeyValue(c.getCurrentNode().opacityProperty(), 0, Interpolator.EASE_BOTH),
@@ -215,38 +282,12 @@ public enum ContainerAnimations {
new KeyValue(c.getCurrentNode().translateXProperty(), 0, Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().translateXProperty(), 50, Interpolator.EASE_BOTH),
new KeyValue(c.getCurrentNode().opacityProperty(), 1, Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH))));
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH)));
}
};
private final AnimationProducer animationProducer;
private ContainerAnimations opposite;
ContainerAnimations(Consumer<AnimationHandler> init, Function<AnimationHandler, List<KeyFrame>> animationProducer) {
this.animationProducer = new AnimationProducer() {
@Override
public void init(AnimationHandler handler) {
init.accept(handler);
}
@Override
public List<KeyFrame> animate(AnimationHandler handler) {
return animationProducer.apply(handler);
}
@Override
public @Nullable AnimationProducer opposite() {
return opposite != null ? opposite.getAnimationProducer() : null;
}
};
}
public AnimationProducer getAnimationProducer() {
return animationProducer;
}
public ContainerAnimations getOpposite() {
return opposite;
}
static {
NONE.opposite = NONE;
FADE.opposite = FADE;
@@ -257,4 +298,15 @@ public enum ContainerAnimations {
ZOOM_IN.opposite = ZOOM_OUT;
ZOOM_OUT.opposite = ZOOM_IN;
}
@Override
public abstract void init(AnimationHandler handler);
@Override
public abstract List<KeyFrame> animate(AnimationHandler handler);
@Override
public @Nullable ContainerAnimations opposite() {
return opposite;
}
}
@@ -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) {
@@ -35,6 +35,8 @@ public class FontComboBox extends JFXComboBox<String> {
private boolean loaded = false;
public FontComboBox() {
setMinWidth(260);
styleProperty().bind(Bindings.concat("-fx-font-family: \"", valueProperty(), "\""));
setCellFactory(listView -> new JFXListCell<String>() {
@@ -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() {
@@ -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
@@ -126,7 +126,7 @@ public class Navigator extends TransitionPane {
if (obj instanceof AnimationProducer) {
setContent(node, (AnimationProducer) obj);
} else {
setContent(node, ContainerAnimations.NONE.getAnimationProducer());
setContent(node, ContainerAnimations.NONE);
}
NavigationEvent navigated = new NavigationEvent(this, node, Navigation.NavigationDirection.PREVIOUS, NavigationEvent.NAVIGATED);
@@ -146,12 +146,12 @@ public class SpinnerPane extends Control {
observer = FXUtils.observeWeak(() -> {
if (getSkinnable().getFailedReason() != null) {
root.setContent(failedPane, ContainerAnimations.FADE.getAnimationProducer());
root.setContent(failedPane, ContainerAnimations.FADE);
failedReasonLabel.setText(getSkinnable().getFailedReason());
} else if (getSkinnable().isLoading()) {
root.setContent(topPane, ContainerAnimations.FADE.getAnimationProducer());
root.setContent(topPane, ContainerAnimations.FADE);
} else {
root.setContent(contentPane, ContainerAnimations.FADE.getAnimationProducer());
root.setContent(contentPane, ContainerAnimations.FADE);
}
}, getSkinnable().loadingProperty(), getSkinnable().failedReasonProperty());
}
@@ -49,13 +49,10 @@ import org.jackhuang.hmcl.ui.construct.Navigator;
import org.jackhuang.hmcl.ui.construct.StackContainerPane;
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;
@@ -180,23 +177,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 +205,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 ====
@@ -457,7 +428,7 @@ public class DecoratorController {
public void startWizard(WizardProvider wizardProvider, String category) {
FXUtils.checkFxUserThread();
navigator.navigate(new DecoratorWizardDisplayer(wizardProvider, category), ContainerAnimations.FADE.getAnimationProducer());
navigator.navigate(new DecoratorWizardDisplayer(wizardProvider, category), ContainerAnimations.FADE);
}
// ==== Authlib Injector DnD ====
@@ -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();
@@ -174,11 +164,11 @@ public class DecoratorSkin extends SkinBase<Decorator> {
if (s.isAnimate()) {
AnimationProducer animation;
if (skinnable.getNavigationDirection() == Navigation.NavigationDirection.NEXT) {
animation = ContainerAnimations.SWIPE_LEFT_FADE_SHORT.getAnimationProducer();
animation = ContainerAnimations.SWIPE_LEFT_FADE_SHORT;
} else if (skinnable.getNavigationDirection() == Navigation.NavigationDirection.PREVIOUS) {
animation = ContainerAnimations.SWIPE_RIGHT_FADE_SHORT.getAnimationProducer();
animation = ContainerAnimations.SWIPE_RIGHT_FADE_SHORT;
} else {
animation = ContainerAnimations.FADE.getAnimationProducer();
animation = ContainerAnimations.FADE;
}
skinnable.setNavigationDirection(Navigation.NavigationDirection.START);
navBarPane.setContent(node, animation);
@@ -202,9 +192,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
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.setGraphic(SVG.MINUS.createIcon(Theme.foregroundFillBinding(), -1, -1));
btnMin.getStyleClass().add("jfx-decorator-button");
btnMin.setOnAction(e -> skinnable.minimize());
@@ -310,13 +298,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 +314,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();
}
}
}
@@ -36,7 +36,7 @@ public abstract class DecoratorTabPage extends DecoratorTransitionPage implement
if (newValue.getNode() != null) {
onNavigating(getCurrentPage());
if (getCurrentPage() != null) getCurrentPage().fireEvent(new Navigator.NavigationEvent(null, getCurrentPage(), Navigation.NavigationDirection.NEXT, Navigator.NavigationEvent.NAVIGATING));
navigate(newValue.getNode(), ContainerAnimations.FADE.getAnimationProducer());
navigate(newValue.getNode(), ContainerAnimations.FADE);
onNavigated(getCurrentPage());
if (getCurrentPage() != null) getCurrentPage().fireEvent(new Navigator.NavigationEvent(null, getCurrentPage(), Navigation.NavigationDirection.NEXT, Navigator.NavigationEvent.NAVIGATED));
}
@@ -74,7 +74,7 @@ public class DecoratorWizardDisplayer extends DecoratorTransitionPage implements
@Override
public void navigateTo(Node page, Navigation.NavigationDirection nav) {
displayer.navigateTo(page, nav);
navigate(page, nav.getAnimation().getAnimationProducer());
navigate(page, nav.getAnimation());
String prefix = category == null ? "" : category + " - ";
@@ -97,7 +97,7 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
tab.select(newGameTab);
FXUtils.onChangeAndOperate(tab.getSelectionModel().selectedItemProperty(), newValue -> {
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE.getAnimationProducer());
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE);
});
{
@@ -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,18 @@ 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.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;
@@ -55,14 +61,20 @@ import org.jackhuang.hmcl.ui.wizard.WizardPage;
import org.jackhuang.hmcl.util.Holder;
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;
import static org.jackhuang.hmcl.util.StringUtils.isBlank;
public final class VersionsPage extends BorderPane implements WizardPage, Refreshable {
private final String gameVersion;
@@ -86,6 +98,11 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
private final VersionList<?> versionList;
private CompletableFuture<?> executor;
private final TransitionPane toolbarPane;
private final HBox searchBar;
private final JFXTextField searchField;
private boolean isSearching = false;
public VersionsPage(Navigation navigation, String title, String gameVersion, DownloadProvider downloadProvider, String libraryId, Runnable callback) {
this.title = title;
this.gameVersion = gameVersion;
@@ -136,6 +153,9 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
list.getStyleClass().add("jfx-list-view-float");
VBox.setVgrow(list, Priority.ALWAYS);
// 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);
centrePane.getContent().setAll(checkPane, list);
}
@@ -163,7 +183,15 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
this.setCenter(root);
versionList = downloadProvider.getVersionListById(libraryId);
if (versionList.hasType()) {
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(checkPane, list);
} else {
centrePane.getContent().setAll(list);
@@ -178,7 +206,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
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)
@@ -187,6 +215,50 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
callback.run();
});
{
toolbarPane = new TransitionPane();
searchBar = new HBox();
searchBar.setAlignment(Pos.CENTER);
searchBar.setPadding(new Insets(0, 5, 0, 5));
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 -> {
toolbarPane.setContent(checkPane, ContainerAnimations.FADE);
isSearching = false;
searchField.clear();
list.getItems().setAll(loadVersions());
});
onEscPressed(searchField, closeSearchBar::fire);
searchBar.getChildren().setAll(searchField, closeSearchBar);
JFXButton searchButton = new JFXButton(i18n("search"));
searchButton.getStyleClass().add("jfx-tool-bar-button");
searchButton.setGraphic(wrap(SVG.MAGNIFY.createIcon(Theme.blackFill(), -1, -1)));
searchButton.setOnAction(e -> {
toolbarPane.setContent(searchBar, ContainerAnimations.FADE);
searchField.requestFocus();
});
checkPane.getChildren().add(checkPane.getChildren().size() - 1, searchButton);
centrePane.getContent().remove(checkPane);
toolbarPane.setContent(checkPane, ContainerAnimations.FADE);
centrePane.getContent().add(0, toolbarPane);
PauseTransition pause = new PauseTransition(Duration.millis(100));
pause.setOnFinished(e -> search());
searchField.textProperty().addListener((observable, oldValue, newValue) -> {
pause.setRate(1);
pause.playFromStart();
});
}
refresh();
}
@@ -210,7 +282,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
@Override
public void refresh() {
VersionList<?> currentVersionList = versionList;
root.setContent(spinner, ContainerAnimations.FADE.getAnimationProducer());
root.setContent(spinner, ContainerAnimations.FADE);
executor = currentVersionList.refreshAsync(gameVersion).whenComplete((result, exception) -> {
if (exception == null) {
List<RemoteVersion> items = loadVersions();
@@ -218,7 +290,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
Platform.runLater(() -> {
if (versionList != currentVersionList) return;
if (currentVersionList.getVersions(gameVersion).isEmpty()) {
root.setContent(emptyPane, ContainerAnimations.FADE.getAnimationProducer());
root.setContent(emptyPane, ContainerAnimations.FADE);
} else {
if (items.isEmpty()) {
chkRelease.setSelected(true);
@@ -227,14 +299,14 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
} else {
list.getItems().setAll(items);
}
root.setContent(center, ContainerAnimations.FADE.getAnimationProducer());
root.setContent(center, ContainerAnimations.FADE);
}
});
} else {
LOG.warning("Failed to fetch versions list", exception);
Platform.runLater(() -> {
if (versionList != currentVersionList) return;
root.setContent(failedPane, ContainerAnimations.FADE.getAnimationProducer());
root.setContent(failedPane, ContainerAnimations.FADE);
});
}
@@ -265,6 +337,37 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
FXUtils.openLink("https://bmclapidoc.bangbang93.com");
}
private void search() {
isSearching = true;
String queryString = searchField.getText();
if (isBlank(queryString)) {
list.getItems().setAll(loadVersions());
} else {
list.getItems().clear();
Predicate<String> predicate;
if (queryString.startsWith("regex:")) {
try {
Pattern pattern = Pattern.compile(queryString.substring("regex:".length()));
predicate = s -> pattern.matcher(s).find();
} catch (Throwable e) {
LOG.warning("Illegal regular expression", e);
return;
}
} else {
String lowerQueryString = queryString.toLowerCase(Locale.ROOT);
predicate = s -> s.toLowerCase(Locale.ROOT).contains(lowerQueryString);
}
for (RemoteVersion version : loadVersions()) {
if (predicate.test(version.getSelfVersion())) {
list.getItems().add(version);
}
}
}
}
private static class RemoteVersionListCell extends ListCell<RemoteVersion> {
final IconedTwoLineListItem content = new IconedTwoLineListItem();
final RipplerContainer ripplerContainer = new RipplerContainer(content);
@@ -272,8 +375,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));
@@ -307,14 +414,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 +451,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() {
@@ -66,7 +66,7 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
gameTab.initializeIfNeeded();
gameTab.getNode().loadVersion(Profiles.getSelectedProfile(), null);
FXUtils.onChangeAndOperate(tab.getSelectionModel().selectedItemProperty(), newValue -> {
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE.getAnimationProducer());
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE);
});
{
@@ -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;
@@ -66,7 +68,6 @@ import java.util.stream.IntStream;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.ui.FXUtils.SINE;
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class MainPage extends StackPane implements DecoratorPage {
@@ -103,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.getAnimationProducer());
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();
@@ -224,7 +251,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
StackPane.setAlignment(svg, Pos.CENTER_RIGHT);
graphic.getChildren().setAll(svg);
graphic.setTranslateX(12);
runInFX(() -> FXUtils.installFastTooltip(menuButton, i18n("version.switch")));
FXUtils.installFastTooltip(menuButton, i18n("version.switch"));
menuButton.setGraphic(graphic);
launchPane.getChildren().setAll(launchButton, separator, menuButton);
@@ -298,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.getAnimationProducer());
}
}
@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))
@@ -165,7 +165,7 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
downloadItem.setActionButtonVisible(false);
downloadItem.setTitle(i18n("download"));
downloadItem.setOnAction(e -> Controllers.navigate(Controllers.getDownloadPage()));
runInFX(() -> FXUtils.installFastTooltip(downloadItem, i18n("download.hint")));
FXUtils.installFastTooltip(downloadItem, i18n("download.hint"));
// fifth item in left sidebar
AdvancedListItem launcherSettingsItem = new AdvancedListItem();
@@ -1,4 +1,4 @@
package moe.mickey.minecraft.skin.fx;
package org.jackhuang.hmcl.ui.skin;
import javafx.event.Event;
import javafx.event.EventHandler;
@@ -1,4 +1,4 @@
package moe.mickey.minecraft.skin.fx;
package org.jackhuang.hmcl.ui.skin;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
@@ -1,4 +1,4 @@
package moe.mickey.minecraft.skin.fx;
package org.jackhuang.hmcl.ui.skin;
import javafx.animation.AnimationTimer;
@@ -1,4 +1,4 @@
package moe.mickey.minecraft.skin.fx;
package org.jackhuang.hmcl.ui.skin;
import javafx.scene.*;
import javafx.scene.image.Image;
@@ -1,4 +1,4 @@
package moe.mickey.minecraft.skin.fx;
package org.jackhuang.hmcl.ui.skin;
import javafx.scene.shape.Mesh;
import javafx.scene.shape.MeshView;
@@ -1,4 +1,4 @@
package moe.mickey.minecraft.skin.fx;
package org.jackhuang.hmcl.ui.skin;
import javafx.scene.Group;
import javafx.scene.Node;
@@ -1,4 +1,4 @@
package moe.mickey.minecraft.skin.fx;
package org.jackhuang.hmcl.ui.skin;
import javafx.scene.image.Image;
import javafx.scene.image.PixelReader;
@@ -1,4 +1,4 @@
package moe.mickey.minecraft.skin.fx;
package org.jackhuang.hmcl.ui.skin;
import javafx.geometry.Point2D;
import javafx.scene.Group;
@@ -1,4 +1,4 @@
package moe.mickey.minecraft.skin.fx;
package org.jackhuang.hmcl.ui.skin;
import javafx.animation.Transition;
import javafx.beans.value.WritableValue;
@@ -1,10 +1,10 @@
package moe.mickey.minecraft.skin.fx.animation;
package org.jackhuang.hmcl.ui.skin.animation;
import javafx.util.Duration;
import moe.mickey.minecraft.skin.fx.FunctionHelper;
import moe.mickey.minecraft.skin.fx.SkinAnimation;
import moe.mickey.minecraft.skin.fx.SkinCanvas;
import moe.mickey.minecraft.skin.fx.SkinTransition;
import org.jackhuang.hmcl.ui.skin.FunctionHelper;
import org.jackhuang.hmcl.ui.skin.SkinAnimation;
import org.jackhuang.hmcl.ui.skin.SkinCanvas;
import org.jackhuang.hmcl.ui.skin.SkinTransition;
public final class SkinAniRunning extends SkinAnimation {
@@ -1,10 +1,10 @@
package moe.mickey.minecraft.skin.fx.animation;
package org.jackhuang.hmcl.ui.skin.animation;
import javafx.util.Duration;
import moe.mickey.minecraft.skin.fx.FunctionHelper;
import moe.mickey.minecraft.skin.fx.SkinAnimation;
import moe.mickey.minecraft.skin.fx.SkinCanvas;
import moe.mickey.minecraft.skin.fx.SkinTransition;
import org.jackhuang.hmcl.ui.skin.FunctionHelper;
import org.jackhuang.hmcl.ui.skin.SkinAnimation;
import org.jackhuang.hmcl.ui.skin.SkinCanvas;
import org.jackhuang.hmcl.ui.skin.SkinTransition;
public final class SkinAniWavingArms extends SkinAnimation {
@@ -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,7 +66,7 @@ import java.util.Locale;
import java.util.Optional;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
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;
@@ -302,7 +304,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
lblGameVersion.visibleProperty().bind(hasVersion);
gameVersionField.managedProperty().bind(hasVersion);
gameVersionField.visibleProperty().bind(hasVersion);
runInFX(() -> FXUtils.installFastTooltip(gameVersionField, i18n("search.enter")));
FXUtils.installFastTooltip(gameVersionField, i18n("search.enter"));
FXUtils.onChangeAndOperate(getSkinnable().version, version -> {
if (StringUtils.isNotBlank(version.getVersion())) {
@@ -494,6 +496,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,14 +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.runInFX;
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class DownloadPage extends Control implements DecoratorPage {
@@ -241,14 +238,14 @@ public class DownloadPage extends Control implements DecoratorPage {
openMcmodButton.setExternalLink(getSkinnable().translations.getMcmodUrl(getSkinnable().mod));
descriptionPane.getChildren().add(openMcmodButton);
openMcmodButton.setMinWidth(Region.USE_PREF_SIZE);
runInFX(() -> FXUtils.installFastTooltip(openMcmodButton, i18n("mods.mcmod")));
FXUtils.installFastTooltip(openMcmodButton, i18n("mods.mcmod"));
}
JFXHyperlink openUrlButton = new JFXHyperlink(control.page.getLocalizedOfficialPage());
openUrlButton.setExternalLink(getSkinnable().addon.getPageUrl());
descriptionPane.getChildren().add(openUrlButton);
openUrlButton.setMinWidth(Region.USE_PREF_SIZE);
runInFX(() -> FXUtils.installFastTooltip(openUrlButton, control.page.getLocalizedOfficialPage()));
FXUtils.installFastTooltip(openUrlButton, control.page.getLocalizedOfficialPage());
}
SpinnerPane spinnerPane = new SpinnerPane();
@@ -346,7 +343,10 @@ public class DownloadPage extends Control implements DecoratorPage {
pane.getChildren().setAll(FXUtils.limitingSize(imageView, 40, 40), content);
RipplerContainer container = new RipplerContainer(pane);
FXUtils.onClicked(container, () -> Controllers.navigate(new DownloadPage(page, addon, version, callback)));
FXUtils.onClicked(container, () -> {
fireEvent(new DialogCloseEvent());
Controllers.navigate(new DownloadPage(page, addon, version, callback));
});
getChildren().setAll(container);
if (addon != RemoteMod.BROKEN) {
@@ -378,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:
@@ -443,7 +444,7 @@ public class DownloadPage extends Control implements DecoratorPage {
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();
@@ -486,6 +487,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) {
@@ -505,7 +508,6 @@ public class DownloadPage extends Control implements DecoratorPage {
dependencies.put(dependency.getType(), list);
}
DependencyModItem dependencyModItem = new DependencyModItem(selfPage.page, dependency.load(), selfPage.version, selfPage.callback);
FXUtils.onClicked(dependencyModItem, () -> fireEvent(new DialogCloseEvent()));
dependencies.get(dependency.getType()).add(dependencyModItem);
}
@@ -523,8 +525,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);
}
@@ -18,7 +18,6 @@
package org.jackhuang.hmcl.ui.versions;
import javafx.scene.Node;
import javafx.scene.control.Tooltip;
import javafx.scene.image.ImageView;
import org.jackhuang.hmcl.event.Event;
import org.jackhuang.hmcl.setting.Profile;
@@ -34,15 +33,13 @@ import java.util.function.Consumer;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class GameAdvancedListItem extends AdvancedListItem {
private final Tooltip tooltip;
private final ImageView imageView;
private final WeakListenerHolder holder = new WeakListenerHolder();
private Profile profile;
@SuppressWarnings("unused")
private Consumer<Event> onVersionIconChangedListener;
public GameAdvancedListItem() {
tooltip = new Tooltip();
Pair<Node, ImageView> view = createImageView(null);
setLeftGraphic(view.getKey());
imageView = view.getValue();
@@ -63,17 +60,13 @@ public class GameAdvancedListItem extends AdvancedListItem {
}
if (version != null && Profiles.getSelectedProfile() != null &&
Profiles.getSelectedProfile().getRepository().hasVersion(version)) {
FXUtils.installFastTooltip(this, tooltip);
setTitle(version);
setSubtitle(null);
setTitle(i18n("version.manage.manage"));
setSubtitle(version);
imageView.setImage(Profiles.getSelectedProfile().getRepository().getVersionIconImage(version));
tooltip.setText(version);
} else {
Tooltip.uninstall(this,tooltip);
setTitle(i18n("version.empty"));
setSubtitle(i18n("version.empty.add"));
imageView.setImage(VersionIconType.DEFAULT.getIcon());
tooltip.setText("");
}
}
}
@@ -35,7 +35,6 @@ import org.jackhuang.hmcl.ui.construct.PopupMenu;
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
import org.jackhuang.hmcl.util.Lazy;
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class GameListItemSkin extends SkinBase<GameListItem> {
@@ -82,7 +81,7 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
btnUpgrade.setOnAction(e -> skinnable.update());
btnUpgrade.getStyleClass().add("toggle-icon4");
btnUpgrade.setGraphic(FXUtils.limitingSize(SVG.UPDATE.createIcon(Theme.blackFill(), 24, 24), 24, 24));
runInFX(() -> FXUtils.installFastTooltip(btnUpgrade, i18n("version.update")));
FXUtils.installFastTooltip(btnUpgrade, i18n("version.update"));
right.getChildren().add(btnUpgrade);
}
@@ -92,7 +91,7 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
btnLaunch.getStyleClass().add("toggle-icon4");
BorderPane.setAlignment(btnLaunch, Pos.CENTER);
btnLaunch.setGraphic(FXUtils.limitingSize(SVG.ROCKET_LAUNCH_OUTLINE.createIcon(Theme.blackFill(), 24, 24), 24, 24));
runInFX(() -> FXUtils.installFastTooltip(btnLaunch, i18n("version.launch.test")));
FXUtils.installFastTooltip(btnLaunch, i18n("version.launch.test"));
right.getChildren().add(btnLaunch);
}
@@ -105,7 +104,7 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
btnManage.getStyleClass().add("toggle-icon4");
BorderPane.setAlignment(btnManage, Pos.CENTER);
btnManage.setGraphic(FXUtils.limitingSize(SVG.DOTS_VERTICAL.createIcon(Theme.blackFill(), 24, 24), 24, 24));
runInFX(() -> FXUtils.installFastTooltip(btnManage, i18n("settings.game.management")));
FXUtils.installFastTooltip(btnManage, i18n("settings.game.management"));
right.getChildren().add(btnManage);
}
@@ -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();
}
}
});
}
{
@@ -186,6 +201,18 @@ class ModListPageSkin extends SkinBase<ModListPage> {
}
});
listView.setOnContextMenuRequested(event -> {
ModInfoObject selectedItem = listView.getSelectionModel().getSelectedItem();
if (selectedItem != null && listView.getSelectionModel().getSelectedItems().size() == 1) {
listView.getSelectionModel().clearSelection();
Controllers.dialog(new ModInfoDialog(selectedItem));
}
});
// 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);
}
@@ -204,7 +231,7 @@ class ModListPageSkin extends SkinBase<ModListPage> {
private void changeToolbar(HBox newToolbar) {
Node oldToolbar = toolbarPane.getCurrentNode();
if (newToolbar != oldToolbar) {
toolbarPane.setContent(newToolbar, ContainerAnimations.FADE.getAnimationProducer());
toolbarPane.setContent(newToolbar, ContainerAnimations.FADE);
if (newToolbar == searchBar) {
searchField.requestFocus();
}
@@ -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 {
@@ -78,7 +78,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
tab.select(versionSettingsTab);
FXUtils.onChangeAndOperate(tab.getSelectionModel().selectedItemProperty(), newValue -> {
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE.getAnimationProducer());
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE);
});
listenerHolder.add(EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> checkSelectedVersion(), EventPriority.HIGHEST));
@@ -57,10 +57,27 @@ 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 +109,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 +119,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) {
@@ -185,7 +202,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 +252,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));
{
@@ -443,22 +461,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 +474,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
@@ -519,7 +534,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
FXUtils.unbindEnum(cboLauncherVisibility);
FXUtils.unbindEnum(cboProcessPriority);
lastVersionSetting.usesGlobalProperty().removeListener(specificSettingsListener);
lastVersionSetting.usesGlobalProperty().removeListener(usesGlobalListener);
lastVersionSetting.javaVersionTypeProperty().removeListener(javaListener);
lastVersionSetting.javaDirProperty().removeListener(javaListener);
lastVersionSetting.defaultJavaPathPropertyProperty().removeListener(javaListener);
@@ -528,6 +543,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;
@@ -552,9 +569,10 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
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)
@@ -685,7 +703,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
version = null;
} else {
gameVersionNumber = GameVersionNumber.asGameVersion(repository.getGameVersion(versionId));
version = repository.getVersion(versionId);
version = repository.getResolvedVersion(versionId);
}
try {
@@ -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"),
@@ -83,7 +83,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 -> {
@@ -153,6 +153,6 @@ public final class NativePatcher {
}
public static Library getMesaLoader(JavaRuntime javaVersion, Renderer renderer) {
return getNatives(javaVersion.getPlatform()).get(renderer == Renderer.LLVMPIPE ? "software-renderer-loader" : "mesa-loader");
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;
}
}
@@ -37,17 +37,7 @@ public final class Locales {
/**
* English
*/
public static final SupportedLocale EN = new SupportedLocale(Locale.ROOT);
/**
* Traditional Chinese
*/
public static final SupportedLocale ZH = new SupportedLocale(Locale.TRADITIONAL_CHINESE);
/**
* Simplified Chinese
*/
public static final SupportedLocale ZH_CN = new SupportedLocale(Locale.SIMPLIFIED_CHINESE);
public static final SupportedLocale EN = new SupportedLocale(Locale.ENGLISH);
/**
* Spanish
@@ -64,23 +54,33 @@ public final class Locales {
*/
public static final SupportedLocale JA = new SupportedLocale(Locale.JAPANESE);
public static final List<SupportedLocale> LOCALES = Lang.immutableListOf(DEFAULT, EN, ZH_CN, ZH, ES, RU, JA);
/**
* Traditional Chinese
*/
public static final SupportedLocale ZH = new SupportedLocale(Locale.TRADITIONAL_CHINESE);
/**
* Simplified Chinese
*/
public static final SupportedLocale ZH_CN = new SupportedLocale(Locale.SIMPLIFIED_CHINESE);
public static final List<SupportedLocale> LOCALES = Lang.immutableListOf(DEFAULT, EN, ES, JA, RU, ZH_CN, ZH);
public static SupportedLocale getLocaleByName(String name) {
if (name == null) return DEFAULT;
switch (name.toLowerCase(Locale.ROOT)) {
case "en":
return EN;
case "es":
return ES;
case "ja":
return JA;
case "ru":
return RU;
case "zh":
return ZH;
case "zh_cn":
return ZH_CN;
case "es":
return ES;
case "ru":
return RU;
case "ja":
return JA;
default:
return DEFAULT;
}
@@ -88,11 +88,11 @@ public final class Locales {
public static String getNameByLocale(SupportedLocale locale) {
if (locale == EN) return "en";
else if (locale == ZH) return "zh";
else if (locale == ZH_CN) return "zh_CN";
else if (locale == ES) return "es";
else if (locale == RU) return "ru";
else if (locale == JA) return "ja";
else if (locale == ZH) return "zh";
else if (locale == ZH_CN) return "zh_CN";
else if (locale == DEFAULT) return "def";
else throw new IllegalArgumentException("Unknown locale: " + locale);
}
Binary file not shown.
+66 -30
View File
@@ -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
@@ -24,5 +24,8 @@
-fx-base-disabled-text-fill: rgba(256, 256, 256, 0.7);
-fx-base-text-fill: white;
/* https://github.com/HMCL-dev/HMCL/pull/3423 */
-fx-font-family: -fx-base-font-family;
-theme-thumb: rgba(92, 107, 192, 0.7);
}
@@ -1,30 +0,0 @@
/**
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
.root {
-fx-base-color: %base-color%;
-fx-base-darker-color: derive(-fx-base-color, -10%);
-fx-base-check-color: derive(-fx-base-color, 30%);
-fx-rippler-color: rgba(%base-red%, %base-green%, %base-blue%, 0.3);
-fx-base-rippler-color: derive(%base-rippler-color%, 100%);
-fx-base-disabled-text-fill: %disabled-font-color%;
-fx-base-text-fill: %font-color%;
-theme-thumb: rgba(%base-red%, %base-green%, %base-blue%, 0.7);
%font%
}
@@ -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;
File diff suppressed because it is too large. Load diff
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
@@ -18,31 +18,35 @@
# Contributors: zhixuan2333
about=About
about=について
about.copyright=著作権
about.copyright.statement=Copyright © 2024 huangyuhui。
about.author=Author
about.author.statement=bilibili : @huanghongxun
about.copyright.statement=Copyright © 2025 huangyuhui
about.author=著者
about.author.statement=bilibili @huanghongxun
about.claim=EULA
about.claim.statement=テキスト全体のリンクをクリックします。
about.dependency=Dependencies
about.claim.statement=全文はこのリンクをクリック
about.dependency=依存関係
about.legal=法的承認
about.thanks_to=Thanks to
about.thanks_to.bangbang93.statement=BMCLAPIダウンロードソースプロバイダー。
about.thanks_to.contributors=問題、プルリクエストなどを介してこのプロジェクトに参加したすべての貢献者。
about.thanks_to.contributors.statement=Hello Minecraftをサポートしてくれたオープンソースコミュニティに感謝します!ランチャー
about.thanks_to.gamerteam.statement=デフォルトの背景画像プロバイダー。
about.thanks_to.glavo.statement=HMCLに多くのテクニカルサポートを提供
about.thanks_to=感謝します…
about.thanks_to.bangbang93.statement=BMCLAPIダウンロードミラープロバイダー。寄付をご検討ください!
about.thanks_to.burningtnt.statement=HMCLに多くのテクニカルサポートを提供
about.thanks_to.contributors=GitHubのすべての貢献者
about.thanks_to.contributors.statement=HMCLをサポートしてくれたオープンソースコミュニティに感謝する
about.thanks_to.gamerteam.statement=デフォルトの背景画像プロバイダー
about.thanks_to.glavo.statement=HMCLのメンテナ
about.thanks_to.zekerzhayard.statement=HMCLに多くのテクニカルサポートを提供
about.thanks_to.zkitefly.statement=HMCLに多くのテクニカルサポートを提供。
about.thanks_to.mcmod=mcmod.cn
about.thanks_to.mcmod.statement=Mod中国語の名前翻訳プロバイダー。
about.thanks_to.red_lnn.statement=デフォルトの背景画像プロバイダー。
about.thanks_to.zkitefly.statement=HMCLドキュメントメンテナ
about.thanks_to.mcbbs=MCBBS (Minecraft中国語フォーラム)
about.thanks_to.mcbbs.statement=中国本土ユーザー向けのmcbbs.netダウンロードミラープロバイダー(もう利用できません)
about.thanks_to.mcmod=MCMod (mcmod.cn)
about.thanks_to.mcmod.statement=様々なMODの簡体字中国語名前翻訳とウィキを提供する
about.thanks_to.red_lnn.statement=デフォルトの背景画像プロバイダー
about.thanks_to.shulkersakura.statement=HMCLロゴプロバイダー
about.thanks_to.users=HMCLユーザーグループのメンバー
about.thanks_to.users.statement=寄付、バグレポートなどに感謝します。
about.thanks_to.yushijinhun.statement=authlib-インジェクターのサポート
about.thanks_to.users.statement=寄付、バグレポートなどに感謝します
about.thanks_to.yushijinhun.statement=authlib-injector関連のサポートを提供する
about.open_source=オープンソース
about.open_source.statement=GPL v3(https://github.com/HMCL-dev/HMCL/)
about.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL)
account=アカウント
account.cape=マント
@@ -136,6 +140,7 @@ assets.index.malformed=アセットインデックスの形式が正しくあり
button.cancel=キャンセル
button.change_source=ダウンロードソースの変更
button.clear=クリア
button.copy_and_exit=コピーして終了
button.delete=削除
button.edit=編集
button.install=インストール
@@ -144,19 +149,23 @@ button.no=いいえ
button.ok=OK
button.refresh=更新
button.remove=削除
button.remove.confirm=削除してもよろしいですか?この操作をロールバックすることはできません!
button.remove.confirm=本当に完全に削除しますか?この操作は元に戻せません!
button.retry=リトライ
button.save=保存
button.save_as=名前を付けて保存
button.select_all=すべて選択
button.view=読む
button.yes=はい
chat=グループチャット
color.recent=推奨
color.custom=カスタムカラー
crash.NoClassDefFound=「HelloMinecraft!Launcher」ソフトウェアが破損していないことを確認してください。
crash.user_fault=OSまたはJava環境が正しくインストールされていない可能性があり、クラッシュする可能性があります。Javaランタイム環境またはコンピュータを確認してください。
crash.NoClassDefFound=このソフトウェアの整合性を確認するか、Javaの更新をお試しください。
crash.user_fault=JavaまたはOSの環境が壊れているため、ランチャーがクラッシュしました。JavaまたはOSが正しくインストールされているかご確認ください。
curse.category.0=すべて
curse.category.0=全て
# https://addons-ecs.forgesvc.net/api/v2/category/section/4471
curse.category.4474=Sci-Fi
@@ -350,12 +359,9 @@ game.crash.reason.entity=エンティティが原因でゲームを実行でき
game.crash.reason.fabric_version_0_12=Fabric 0.12以降は、現在インストールされているmodと互換性がありません。ファブリックを0.11.7にダウングレードする必要があります。
game.crash.reason.fabric_warnings=Fabricはいくつかの警告を出します:\n%1$s
game.crash.reason.modmixin_failure=mod インジェクションの失敗により、現在のゲームを続行できません。\nこれは通常、MOD にバグがあるか、現在の環境と互換性がないことを意味します。\n間違った mod のログを確認できます。
game.crash.reason.file_or_content_verification_failed=一部のファイルまたはコンテンツの検証に失敗したため、現在のゲームに問題があります。\nこのバージョン (mod を含む) を削除して再ダウンロードするか、再ダウンロード時にプロキシを使用するなどしてください。
game.crash.reason.mod_repeat_installation=現在のゲームには複数の同一の Mod が繰り返しインストールされており、各 Mod は 1 回しか表示できません。ゲームを開始する前に繰り返しの Mod を削除してください。
game.crash.reason.forge_error=Forge がエラー メッセージを表示した可能性があります。 \nログを表示し、エラー レポートのログ情報に従って対応するアクションを実行できます。 \nエラー メッセージが表示されない場合は、エラー レポートをチェックして、エラーがどのように発生したかを知ることができます。\n%1$s
game.crash.reason.mod_resolution0=mod の問題により、現在のゲームを続行できません。 \n間違った mod のログを確認できます。
game.crash.reason.mod_profile_causes_game_crash=mod プロファイルに問題があるため、現在のゲームを続行できません。 \n障害のある mod とその構成ファイルのログを確認できます。
game.crash.reason.fabric_reports_an_error_and_gives_a_solution=Forge がエラー メッセージを表示した可能性があります。 \nログを表示し、エラー レポートのログ情報に従って対応するアクションを実行できます。 \nエラー メッセージが表示されない場合は、エラー レポートをチェックして、エラーがどのように発生したかを知ることができます。
game.crash.reason.java_version_is_too_high=Java のバージョンが高すぎて実行を継続できないため、現在のゲームがクラッシュしました。 \nゲームを起動する前に、グローバル ゲーム設定またはゲーム固有の設定の Java パス タブで Java の以前のバージョンに切り替えてください。 \nそうでない場合は、<a href="https://www.java.com/download/">java.com (Java8)</a> または <a href="https://bell-sw.com/pages/downloads/#downloads">BellSoft Liberica Full JRE (Java17)</a> およびその他のプラットフォームをダウンロードしてインストールします (インストール後にランチャーを再起動します)。
game.crash.reason.mod_name=現在のゲームはModファイル名の問題で続行できません。\nModファイル名は、英文の全半角の大文字と小文字(Aa ~ Zz)、数字(0 ~ 9)、横線(-)、アンダースコア(_)、点(.)のみを使用してください。\n上記のコンプライアンス文字をModフォルダに追加してください。
game.crash.reason.incomplete_forge_installation=Forge / NeoForge のインストールが不完全なため、現在のゲームを続行できません。\nバージョン設定 - 自動インストールで Forge / NeoForge をアンインストールしてから再インストールしてください。
@@ -400,7 +406,6 @@ game.crash.reason.stacktrace=不明。[ログ]ボタンをクリックすると
game.crash.reason.too_old_java=Java仮想マシンのバージョンが低すぎるため、ゲームを実行できません。\nゲーム設定でJava仮想マシンの新しいバージョン(%1$s)に切り替えて、ゲームを再起動する必要があります。そうでない場合は、オンラインでダウンロードできます。
game.crash.reason.unknown=不明。「ログ」ボタンをクリックすると詳細を確認できます。
game.crash.reason.unsatisfied_link_error=必要なネイティブライブラリがないため、ゲームを実行できません。\n不足しているネイティブライブラリ:%1$s。\nゲーム設定でネイティブライブラリパスオプションを変更した場合は、元に戻すことをお勧めします。標準モードに切り替えます。\nすでに標準モードになっている場合は、不足しているネイティブライブラリがmodまたはゲームに属しているかどうかを確認してください。HMCLが原因であることが確実な場合は、フィードバックをお送りください。\nネイティブライブラリパスを本当にカスタマイズする必要がある場合は、ゲームに必要なすべてのネイティブライブラリをディレクトリに配置する必要があります。
game.crash.reason.failed_to_load_a_library=ネイティブ ライブラリのロードに失敗したため、ゲームを実行できません。\nゲーム設定でネイティブライブラリパスオプションを変更した場合は、元に戻すことをお勧めします。標準モードに切り替えます。\nすでに標準モードになっている場合は、不足しているネイティブライブラリがmodまたはゲームに属しているかどうかを確認してください。HMCLが原因であることが確実な場合は、フィードバックをお送りください。\nネイティブライブラリパスを本当にカスタマイズする必要がある場合は、ゲームに必要なすべてのネイティブライブラリをディレクトリに配置する必要があります。
game.crash.title=ゲームがクラッシュしました
game.directory=ゲームディレクトリ
game.version=ゲームバージョン
@@ -455,6 +460,36 @@ install.new_game.malformed=無効な名前
install.select=操作を選択します
install.success=正常にインストールされました
java.add=Javaの追加
java.add.failed=このJavaは無効であるか、現在のプラットフォームと互換性がない。
java.disable=無効化
java.disable.confirm=本当にこのJavaを無効にしますか?
java.disabled.management=無効なJava
java.disabled.management.remove=このJavaをリストから削除する
java.disabled.management.restore=有効化
java.download=Javaをダウンロード
java.download.load_list.failed=バージョンリストの読み込みに失敗しました
java.download.more=その他のJavaディストリビューション
java.download.prompt=ダウンロードしたいJavaのバージョンを選択してください:
java.download.distribution=配布の種類
java.download.version=バージョン
java.download.packageType=パッケージの種類
java.management=Javaの管理
java.info.architecture=アーキテクチャ
java.info.vendor=発行元
java.info.version=バージョン
java.info.disco.distribution=配布の種類
java.install=Javaのインストール
java.install.archive=場所
java.install.failed.exists=この名前はすでに使用されている
java.install.failed.invalid=このアーカイブは有効なJavaインストール・パッケージではないため、インストールできません。
java.install.failed.unsupported_platform=このJavaは現在のプラットフォームと互換性がないため、インストールできません。
java.install.name=名前
java.install.warning.invalid_character=名前に不正な文字が含まれています
java.reveal=Javaディレクトリを表示する
java.uninstall=このJavaをアンインストールする
java.uninstall.confirm=本当にこのJavaをアンインストールしますか?この操作は元に戻せません!
lang=日本語
lang.default=システム言語を使用する
@@ -467,8 +502,8 @@ launch.advice.forge2760_liteloader=Forge 2760以降はLiteLoaderと互換性が
launch.advice.forge28_2_2_optifine=Forge28.2.2以降のバージョンはOptiFineと互換性がありません。Forgeを28.2.1以前のバージョンにダウングレードすることを検討してください。
launch.advice.java8_1_13=Minecraft 1.13以降は、Java8以降でのみ実行できます。
launch.advice.java8_51_1_13=Minecraft 1.13は、1.8.0_51より前のJava8でクラッシュする可能性があります。最新バージョンのJava8をインストールしてください。
launch.advice.java9=Java9以降のバージョンのJavaでMinecraft1.12以前を起動することはできません。
ゲームを高速化するには、launch.advice.newer_java=Java8をお勧めします。多くのMinecraft1.12以降、およびほとんどのModには、Java8が必要です。
launch.advice.java9=Java9以降のバージョンのJavaでMinecraft1.12以前を起動することはできません。ゲームを高速化するには、Java8をお勧めします。
launch.advice.newer_java=多くのMinecraft1.12以降、およびほとんどのModには、Java8が必要です。
launch.advice.not_enough_space=割り当てたメモリが多すぎます。物理メモリサイズが%dMBであるため、ゲームがクラッシュする可能性があります。
launch.advice.too_large_memory_for_32bit=32ビットJavaランタイム環境が原因で、割り当てたメモリが多すぎるため、ゲームがクラッシュする可能性があります。32ビットシステムの最大メモリ容量は1024MBです。
launch.advice.vanilla_linux_java_8=Linux x86-64の場合、Minecraft1.12.2以下はJava8でのみ実行できます。\nJava9以降のバージョンでは、liblwjgl.soなどの32ビットネイティブライブラリをロードできません。
@@ -500,13 +535,14 @@ 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=キャッシュ用のディレクトリ
launcher.cache_directory.clean=クリアランス
launcher.cache_directory.choose=キャッシュするディレクトリを選択します
launcher.cache_directory.default=標準(%AppData%/.minecraft または ~/.minecraft)
launcher.cache_directory.default=標準(%APPDATA%/.minecraft または ~/.minecraft)
launcher.cache_directory.disabled=無効(常にゲームパスを使用する)
launcher.cache_directory.invalid=無効なディレクトリ。デフォルト設定の復元。
launcher.contact=お問い合わせ
@@ -581,8 +617,6 @@ modpack.scan=このmodpackをスキャンしています
modpack.task.install=Modpackのインポート
modpack.task.install.error=このmodpackファイルは認識できません。CurseおよびMultiMCmodpackのみがサポートされています。
modpack.type.curse=Curse
modpack.type.curse.completion=Cursemodpackに関連するファイルをインストールします
modpack.type.curse.tolerable_error=このmodpackのすべてのファイルのダウンロードを完了できません。対応するゲームバージョンを開始するときに、ダウンロードを再試行できます。ネットワークの問題により、数回再試行する場合があります。
modpack.type.curse.error=このmodpackをインストールできません。再試行してください。
modpack.type.curse.not_found=必要なリソースの一部が欠落しているため、ダウンロードできませんでした。最新バージョンまたは他のmodpackを検討してください。
modpack.type.manual.warning=このmodpackをインポートする代わりに、おそらくこのmodpackファイルを直接解凍する必要があります。そして、バンドルされたランチャーを使用してゲームを起動します。このmodpackは、ランチャーによってエクスポートされるのではなく、.minecraftディレクトリを圧縮することによって手動で作成されます。HMCLはこのmodpackのインポートを試みることができます、続行しますか?
@@ -632,7 +666,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
@@ -822,7 +857,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=タイトルの透明性
@@ -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,24 +20,28 @@
about=О лаунчере
about.copyright=Авторские права
about.copyright.statement=Авторские права © 2024 huangyuhui.
about.copyright.statement=Авторские права © 2025 huangyuhui.
about.author=Автор
about.author.statement=bilibili: @huanghongxun
about.author.statement=bilibili @huanghongxun
about.claim=EULA
about.claim.statement=Кликните на ссылку, чтобы увидеть весь текст.
about.dependency=Зависимости
about.legal=Юридическое подтверждение
about.thanks_to=Отдельная благодарность
about.thanks_to.bangbang93.statement=За предоставление API скачивания BMCLAPI. Рассмотрите возможность пожертвования.
about.thanks_to.bangbang93.statement=За предоставление зеркала загрузки BMCLAPI, пожалуйста, подумайте о пожертвовании!
about.thanks_to.burningtnt.statement=Оказал большую техническую поддержку HMCL.
about.thanks_to.contributors=Всем участникам на GitHub
about.thanks_to.contributors.statement=Без потрясающего сообщества с открытым исходным кодом, Hello Minecraft! Launcher не смог бы добраться так далеко.
about.thanks_to.contributors.statement=Без потрясающего сообщества с открытым исходным кодом, HMCL не смог бы добраться так далеко.
about.thanks_to.gamerteam.statement=За предоставление фонового изображения.
about.thanks_to.glavo.statement=Оказал большую техническую поддержку HMCL.
about.thanks_to.glavo.statement=Отвечает за поддержание HMCL.
about.thanks_to.zekerzhayard.statement=Оказал большую техническую поддержку HMCL.
about.thanks_to.zkitefly.statement=Оказал большую техническую поддержку HMCL.
about.thanks_to.mcmod=mcmod.cn
about.thanks_to.mcmod.statement=За предоставление перевода на китайский и вики для различных модов.
about.thanks_to.zkitefly.statement=Отвечает за поддержание документации HMCL.
about.thanks_to.mcbbs=MCBBS (Китайский форум Minecraft)
about.thanks_to.mcbbs.statement=За предоставление зеркала загрузки mcbbs.net для пользователей материкового Китая. (Больше недоступно)
about.thanks_to.mcmod=MCMod (mcmod.cn)
about.thanks_to.mcmod.statement=За предоставление упрощенного китайского перевода и вики для различных модов.
about.thanks_to.red_lnn.statement=За предоставление фонового изображения.
about.thanks_to.shulkersakura.statement=За предоставление логотипа для HMCL.
about.thanks_to.users=Членам группы пользователей HMCL
about.thanks_to.users.statement=Спасибо за пожертвования, отчёты об ошибках и так далее.
about.thanks_to.yushijinhun.statement=За предоставление поддержки, связанной с authlib-injector.
@@ -156,8 +160,11 @@ button.retry=Повторить снова
button.save=Сохранить
button.save_as=Сохранить как
button.select_all=Выбрать все
button.view=Просмотреть
button.yes=Да
chat=Групповой чат
color.recent=Рекомендуемые
color.custom=Пользовательский цвет
@@ -369,13 +376,10 @@ game.crash.reason.fabric_version_0_12=Fabric 0.12 (и выше) несовмес
game.crash.reason.mixin_apply_mod_failed=Текущая игра не может продолжать работать из-за невозможности применения мода %1$s с помощью Mixin.\nВы можете попробовать удалить или обновить этот мод, чтобы решить проблему.
game.crash.reason.fabric_warnings=Предупреждения от Fabric:\n%1$s
game.crash.reason.modmixin_failure=Текущая игра не может продолжать работать из-за некоторых сбоев внедрения модов.\nОбычно это означает, что мод содержит ошибку или несовместим с текущей средой.\nВы можете проверить журнал на наличие неправильного мода.
game.crash.reason.file_or_content_verification_failed=В текущей игре возникла проблема, поскольку не удалось проверить некоторые файлы или содержимое.\nПожалуйста, попробуйте удалить эту версию (включая моды) и загрузить заново, или попробуйте использовать прокси при повторной загрузке и т. д.
game.crash.reason.mod_repeat_installation=В текущей игре неоднократно установлено несколько одинаковых модов, и каждый мод может появиться только один раз. Пожалуйста, удалите повторяющиеся моды перед запуском игры.
game.crash.reason.need_jdk11=Текущая игра не может продолжать работать из-за неподходящей версии виртуальной машины Java.\nВам необходимо загрузить и установить Java 11, а также установить версию Java, начинающуюся с 11, в глобальных (конкретных) настройках игры.
game.crash.reason.forge_error=Возможно, Forge предоставил сообщение об ошибке.\nВы можете просмотреть журнал и выполнить соответствующие действия в соответствии с информацией журнала в отчете об ошибках.\nЕсли вы не видите сообщения об ошибке, вы можете просмотреть отчет об ошибке, чтобы узнать, как она возникла.\n%1$s
game.crash.reason.mod_resolution0=Текущая игра не может продолжать работать из-за некоторых проблем с модом.\nВы можете проверить журнал на наличие неправильного мода.
game.crash.reason.mod_profile_causes_game_crash=Текущая игра не может продолжать работать из-за проблемы с профилем мода.\nВы можете проверить журнал на наличие неисправного мода и его конфигурационного файла.
game.crash.reason.fabric_reports_an_error_and_gives_a_solution=Forge мог выдать сообщение об ошибке.\nВы можете просмотреть журнал и выполнить соответствующие действия в соответствии с информацией журнала в отчете об ошибках.\nЕсли вы не видите сообщения об ошибке, вы можете просмотреть отчет об ошибке, чтобы узнать, как она возникла.
game.crash.reason.java_version_is_too_high=Текущая игра аварийно завершилась из-за того, что версия Java слишком высока и не может продолжать работать.\nПеред запуском игры переключитесь на более раннюю версию Java на вкладке "Путь к Java" в глобальных настройках игры или в настройках игры.\nЕсли нет, вы можете загрузить его с <a href="https://www.java.com/download/">java.com (Java8)</a> или <a href="https://bell- sw.com/pages/downloads">BellSoft Liberica Full JRE (Java17)</a> и другие платформы, чтобы загрузить и установить ее (перезапустите программу запуска после установки).
game.crash.reason.mod_name=Текущая игра не может продолжать работать из - за проблем с именем файла Mod. Имя файла\nMod должно быть использовано только в английском языке в полном углу с большими буквами (Aa ~ Zz), цифрами (0 ~ 9), горизонтальными линиями (-), подчеркнутыми (_) и точками (.) .\nПожалуйста, добавьте символ соответствия в папку Mod для всех несовместимых имен файлов Mod.
game.crash.reason.incomplete_forge_installation=Текущая игра не может быть продолжена из-за незавершенной установки Forge / NeoForge.\nУдалите и переустановите Forge / NeoForge в настройках версии - Автоматическая установка.
@@ -420,7 +424,6 @@ game.crash.reason.stacktrace=Неизвестно. Вы можете просм
game.crash.reason.too_old_java=Невозможно запустить игру из-за того, что версия виртуальной машины Java слишком низкая.\nYou need to switch to a newer version (%1$s) of Java virtual machine in the game settings and restart the game. If not, you can download it online.
game.crash.reason.unknown=Неизвестно. Вы можете просмотреть подробности, кликнув на кнопку «Журналы».
game.crash.reason.unsatisfied_link_error=Невозможно запустить игру из-за того, что отсутствуют необходимые встроенные библиотеки.\nОтсутствующая нативная библиотека: %1$s.\nЕсли в настройках игры вы изменили параметр пути к нативной библиотеке, лучше переключиться обратно в стандартный режим.\nЕсли это уже стандартный режим, проверьте, принадлежит ли отсутствующая нативная библиотека моду или игре. Если вы уверены, что это произошло по вине HMCL, вы можете написать нам через обратную связь.\nЕсли вам действительно нужно настроить путь к нативным библиотекам, вы должны поместить в каталог все нативные библиотеки, которые требуются игре.
game.crash.reason.failed_to_load_a_library=Не удалось запустить игру, так как не удалось загрузить встроенную библиотеку.\nЕсли в настройках игры вы изменили параметр пути к нативной библиотеке, лучше переключиться обратно в стандартный режим.\nЕсли это уже стандартный режим, проверьте, принадлежит ли отсутствующая нативная библиотека моду или игре. Если вы уверены, что это произошло по вине HMCL, вы можете написать нам через обратную связь.\nЕсли вам действительно нужно настроить путь к нативным библиотекам, вы должны поместить в каталог все нативные библиотеки, которые требуются игре.
game.crash.title=Игра вылетела
game.directory=Путь игры
game.version=Версия игры
@@ -475,6 +478,36 @@ install.new_game.malformed=Недопустимое имя
install.select=Выберите операцию
install.success=Успешно установлено.
java.add=Добавить Java
java.add.failed=Этот Java недопустим или несовместим с текущей платформой.
java.disable=Отключить Java
java.disable.confirm=Вы уверены, что хотите отключить эту Java?
java.disabled.management=Отключенная Java
java.disabled.management.remove=Удалить эту Java из списка
java.disabled.management.restore=Включить эту Java
java.download=Скачать Java
java.download.load_list.failed=Не удалось загрузить список версий
java.download.more=Больше дистрибутивов Java
java.download.prompt=Выберите версию Java, которую вы хотите загрузить:
java.download.distribution=Дистрибуция
java.download.version=Версия
java.download.packageType=Тип упаковки
java.management=Управление Java
java.info.architecture=Архитектура
java.info.vendor=Поставщик
java.info.version=Версия
java.info.disco.distribution=Дистрибуция
java.install=Установите Java
java.install.archive=Расположение
java.install.failed.exists=Это имя уже используется
java.install.failed.invalid=Этот архив не является допустимым установочным пакетом Java, поэтому его установка невозможна.
java.install.failed.unsupported_platform=Эта Java несовместима с текущей платформой, поэтому ее невозможно установить.
java.install.name=Название
java.install.warning.invalid_character=Неправильный символ в названии
java.reveal=Откройте каталог Java
java.uninstall=Удалить Java
java.uninstall.confirm=Вы уверены, что хотите удалить эту Java? Это действие нельзя отменить!
lang=Русский
lang.default=Использовать язык системы
@@ -524,13 +557,14 @@ 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=Папка с кэшем
launcher.cache_directory.clean=Очистить кэш
launcher.cache_directory.choose=Выберите папку для кэша
launcher.cache_directory.default=По умолчанию (%AppData%/.minecraft или ~/.minecraft)
launcher.cache_directory.default=По умолчанию (%APPDATA%/.minecraft или ~/.minecraft)
launcher.cache_directory.disabled=Отключено
launcher.cache_directory.invalid=Не удаётся создать папку для кэша, возвращаем к значению по умолчанию.
launcher.contact=Связаться с нами
@@ -606,8 +640,6 @@ modpack.scan=Разбор индекса модпака
modpack.task.install=Установить модпак
modpack.task.install.error=Не удаётся идентифицировать этот модпак. Поддерживаются только модпаки Curse, Modrinth, MultiMC и MCBBS.
modpack.type.curse=Curse
modpack.type.curse.completion=Установите файлы, связанные с модпаком Curse
modpack.type.curse.tolerable_error=Мы не можем завершить скачивание всех файлов этого модпака. Вы можете повторить скачивание при запуске соответствующей версии игры. Вы можете повторить попытку несколько раз из-за проблем с сетью.
modpack.type.curse.error=Невозможно установить этот модпак. Повторите ещё раз.
modpack.type.curse.not_found=Некоторые из необходимых ресурсов отсутствуют и поэтому не могут быть загружены. Скачайте последнюю версию или другие модпаки.
modpack.type.manual.warning=Возможно, вам нужно напрямую распаковать файл этого модпака, а не импортировать его. И запустите игру с помощью прилагаемого лаунчера. Этот модпак создаётся вручную путем сжатия директории .minecraft, а не экспортируется лаунчером. HMCL может попытаться импортировать этот модпак, продолжать?
@@ -665,7 +697,8 @@ mods.add.success=%s был успешно установлен.
mods.category=Категория
mods.check_updates=Проверить обновления
mods.check_updates.current_version=Текущая версия
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=Целевая версия
@@ -712,7 +745,7 @@ world.import.failed=Не удаётся импортировать этот ми
world.import.invalid=Не удаётся разобрать сохранение.
world.info.title=Сведения о мире %s
world.info.basic=Основные сведения
world.info.allow_cheats=Разрешить читы
world.info.allow_cheats=Разрешить команды/читы
world.info.dimension.the_nether=Нижний мир
world.info.dimension.the_end=Край
world.info.difficulty=Сложность
@@ -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=使用者協議
@@ -65,7 +65,7 @@ account.failed.injector_download_failure=無法下載 authlib-injector,請檢
account.failed.invalid_credentials=你的使用者名稱或密碼錯誤,或者登入次數過多被暫時禁止登入,請稍後再試。
account.failed.invalid_password=密碼無效
account.failed.invalid_token=請嘗試登出並重新輸入密碼登入。
account.failed.migration=你的帳戶需要遷移至 Microsoft 帳戶。如果你已經遷移,你需要使用 Microsoft 方式登入遷移後的帳戶。
account.failed.migration=你的帳戶需要遷移至 Microsoft 帳戶。如果你已經遷移,你需要使用遷移後的 Microsoft 帳戶登入。
account.failed.no_character=該帳戶沒有角色。
account.failed.server_disconnected=無法訪問登入伺服器,帳戶資訊重新整理失敗。\n\
你可以選取「再次重新整理帳戶」重新嘗試。\n\
@@ -98,11 +98,11 @@ 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.birth=如何變更帳戶出生日期
account.methods.microsoft.deauthorize=移除應用存取權
account.methods.microsoft.close_page=已完成 Microsoft 帳戶授權,接下來啟動器還需要完成其餘登入步驟。你現在可以關閉本頁面了。
account.methods.microsoft.error.add_family=由於你未滿 18 歲,你的帳戶必須被加入到家庭中才能登入遊戲。你也可以點擊上方連結更改你的帳戶的出生日期,使年齡滿 18 歲以上以繼續登入。
account.methods.microsoft.error.add_family_probably=請檢查你的帳戶設定,如果你未滿 18 歲,你的帳戶必須被加入到家庭中才能登入遊戲。你也可以點擊上方連結更改你的帳戶的出生日期,使年齡滿 18 歲以上以繼續登入。
account.methods.microsoft.error.add_family=由於你未滿 18 歲,你的帳戶必須被加入到家庭中才能登入遊戲。你也可以點擊上方「編輯帳戶配置檔」更改你的帳戶出生日期,使年齡滿 18 歲以上以繼續登入。
account.methods.microsoft.error.add_family_probably=請檢查你的帳戶設定,如果你未滿 18 歲,你的帳戶必須被加入到家庭中才能登入遊戲。你也可以點擊上方「編輯帳戶配置檔」更改你的帳戶出生日期,使年齡滿 18 歲以上以繼續登入。
account.methods.microsoft.error.country_unavailable=你所在的國家或地區不受 Xbox Live 的支援。
account.methods.microsoft.error.missing_xbox_account=你的 Microsoft 帳戶尚未關聯 Xbox 帳戶,你必須先建立 Xbox 帳戶,才能登入遊戲。
account.methods.microsoft.error.no_character=該帳戶未包含 Minecraft: Java 版購買記錄。\n若已購買,則可能未建立遊戲檔案,請點擊上方連結建立。
@@ -112,17 +112,17 @@ account.methods.microsoft.logging_in=登入中……
account.methods.microsoft.makegameidsettings=建立檔案 / 編輯檔案名稱
account.methods.microsoft.hint=請點擊「登入」按鈕,稍後複製此處顯示的代碼,在打開的登入網頁中完成登入過程。\n\
\n\
如果登入 Microsoft 帳戶的令牌洩露,可點擊下方連結解除授權,然後等待令牌過期。\n\
如果登入 Microsoft 帳戶的令牌洩露,可點擊下方「移除應用存取權」,然後等待令牌過期。\n\
\n\
如遇到問題,你可以點擊右上角幫助按鈕進行求助。
account.methods.microsoft.manual=你的代碼為 <b>%1$s</b>,請點擊此處複製。\n\
\n\
點擊「登入」按鈕後,你應該在打開的登入網頁中完成登入過程。如果網頁沒有打開,你可以自行在瀏覽器中訪問 %2$s\n\
\n\
如果登入 Microsoft 帳戶的令牌洩露,可點擊下方連結解除授權,然後等待令牌過期。\n\
如果登入 Microsoft 帳戶的令牌洩露,可點擊下方「移除應用存取權」,然後等待令牌過期。\n\
\n\
如遇到問題,你可以點擊右上角幫助按鈕進行求助。
account.methods.microsoft.profile=帳戶設定頁
account.methods.microsoft.profile=編輯帳戶配置檔
account.methods.microsoft.purchase=購買 Minecraft
account.methods.forgot_password=忘記密碼
account.methods.microsoft.snapshot=你正在使用第三方提供的 HMCL,請下載官方版本進行登入。
@@ -328,7 +328,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 +510,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 +526,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,13 +604,14 @@ 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=檔案下載快取目錄
launcher.cache_directory.clean=清理
launcher.cache_directory.choose=選取檔案下載快取目錄
launcher.cache_directory.default=預設 ("%AppData%/.minecraft" 或 "~/.minecraft")
launcher.cache_directory.default=預設 ("%APPDATA%/.minecraft" 或 "~/.minecraft")
launcher.cache_directory.disabled=停用
launcher.cache_directory.invalid=無法建立自訂的快取目錄,還原至預設設定
launcher.contact=聯絡我們
@@ -625,7 +628,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 群尋求幫助。
@@ -790,7 +793,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=目標版本
@@ -855,7 +859,7 @@ world.import.failed=無法匯入此世界: %s
world.import.invalid=無法識別的存檔壓縮檔
world.info.title=世界 [%s] - 世界資訊
world.info.basic=基本資訊
world.info.allow_cheats=允許指令
world.info.allow_cheats=允許指令(作弊)
world.info.dimension.the_nether=地獄
world.info.dimension.the_end=末地
world.info.difficulty=難易度
@@ -888,7 +892,7 @@ world.name=世界名稱
world.name.enter=輸入世界名稱
world.reveal=開啟目錄
world.show_all=全部顯示
world.time=yyyy 年 M 月 d 日, HH:mm:ss
world.time=yyyy 年 MM 月 dd 日 HH:mm:ss
profile=遊戲目錄
profile.already_exists=該名稱已存在
@@ -972,7 +976,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=中 (平衡)
@@ -1044,7 +1048,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=標題欄透明
@@ -1147,6 +1151,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=用户协议
@@ -65,7 +65,7 @@ account.failed.injector_download_failure=无法下载 authlib-injector,可能
account.failed.invalid_credentials=你的用户名或密码错误,或登录次数过多被暂时禁止登录,请稍后再试。
account.failed.invalid_password=无效的密码
account.failed.invalid_token=请尝试登出并重新输入密码登录
account.failed.migration=你的账户需要迁移至微软账户。如果你已经迁移,你需要使用微软账户登录迁移后的账户。
account.failed.migration=你的账户需要迁移至微软账户。如果你已经迁移,你需要使用迁移后的微软账户登录。
account.failed.no_character=该账户没有角色
account.failed.server_disconnected=无法访问登录服务器,账户信息刷新失败。\n\
你可以选择“再次刷新账户”重新尝试。\n\
@@ -99,11 +99,11 @@ account.copy_uuid=复制该账户的 UUID
account.methods=登录方式
account.methods.authlib_injector=外置登录 (authlib-injector)
account.methods.microsoft=微软账户
account.methods.microsoft.birth=如何修改账户出生日期
account.methods.microsoft.birth=如何更改账户出生日期
account.methods.microsoft.close_page=已完成微软账户授权,接下来启动器还需要完成其余登录步骤。你现在可以关闭本页面了。
account.methods.microsoft.deauthorize=解除账户授权
account.methods.microsoft.error.add_family=请点击上方“账户设置页”更改你的账户出生日期,使年龄满 18 岁以上,或将账户加入到家庭中。\n你可以点击右上角帮助按钮进行求助。
account.methods.microsoft.error.add_family_probably=请点击上方“账户设置页”更改你的账户出生日期,使年龄满 18 岁以上,或将账户加入到家庭中。\n你可以点击右上角帮助按钮进行求助。
account.methods.microsoft.error.add_family=请点击上方“编辑账户个人信息”更改你的账户出生日期,使年龄满 18 岁以上,或将账户加入到家庭中。\n你可以点击右上角帮助按钮进行求助。
account.methods.microsoft.error.add_family_probably=请点击上方“编辑账户个人信息”更改你的账户出生日期,使年龄满 18 岁以上,或将账户加入到家庭中。\n你可以点击右上角帮助按钮进行求助。
account.methods.microsoft.error.country_unavailable=你所在的国家或地区不受 Xbox Live 的支持。
account.methods.microsoft.error.missing_xbox_account=请点击上方“创建档案”关联 Xbox 账户。\n你可以点击右上角帮助按钮进行求助。
account.methods.microsoft.error.no_character=请确认你已经购买了 Minecraft: Java 版。\n若已购买,则可能未创建游戏档案,请点击上方“创建档案”以创建游戏档案。\n你可以点击右上角帮助按钮进行求助。
@@ -116,8 +116,8 @@ account.methods.microsoft.hint=你需要按照以下步骤添加账户:\n\
\ 2. 在弹出的网页中输入 HMCL 显示的代码,并点击“允许访问”;\n\
\ 3. 按照网站的提示登录;\n\
\ 4. 当网站提示“是否允许此应用访问你的信息?”时,请点击“接受”;\n\
\ 5. 在网站提示“大功告成”后,只需等待账户完成添加即可。\n\
若网站提示“出现错误”或账户添加失败时,请重新按照以上步骤重新添加。\n\
\ 5. 在网站提示“大功告成”后,等待账户完成添加即可。\n\
若网站提示“出现错误”或账户添加失败时,请按照以上步骤重新添加。\n\
<b>若设备网络环境不佳,可能会导致网页加载缓慢甚至无法加载,请使用网络代理并重试。</b>\n\
如遇到问题,你可以点击右上角帮助按钮进行求助。
account.methods.microsoft.manual=你需要按照以下步骤添加:\n\
@@ -125,12 +125,12 @@ account.methods.microsoft.manual=你需要按照以下步骤添加:\n\
\ 2. 在弹出的网页中输入 <b>%1$s</b> (已自动复制),并点击“允许访问”;\n\
\ 3. 按照网站的提示登录;\n\
\ 4. 当网站提示“是否允许此应用访问你的信息?”时,请点击“接受”;\n\
\ 5. 在网站提示“大功告成”后,只需等待账户完成添加即可。\n\
\ 5. 在网站提示“大功告成”后,等待账户完成添加即可。\n\
若网站未能显示,请手动在浏览器中打开:%2$s\n\
若网站提示“出现错误”或账户添加失败时,请重新按照以上步骤重新添加。\n\
若网站提示“出现错误”或账户添加失败时,请按照以上步骤重新添加。\n\
<b>若设备网络环境不佳,可能会导致网页加载缓慢甚至无法加载,请使用网络代理并重试。</b>\n\
如遇到问题,你可以点击右上角帮助按钮进行求助。
account.methods.microsoft.profile=账户设置页
account.methods.microsoft.profile=编辑账户个人信息
account.methods.microsoft.purchase=购买 Minecraft
account.methods.forgot_password=忘记密码
account.methods.microsoft.snapshot=你正在使用第三方提供的 HMCL,请下载官方版本来登录微软账户。
@@ -337,7 +337,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 +434,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 +521,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 +537,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 +563,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,13 +615,14 @@ 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=文件下载缓存文件夹
launcher.cache_directory.clean=清理缓存
launcher.cache_directory.choose=选择文件下载缓存文件夹
launcher.cache_directory.default=默认 ("%AppData%/.minecraft" 或 "~/.minecraft")
launcher.cache_directory.default=默认 ("%APPDATA%/.minecraft" 或 "~/.minecraft")
launcher.cache_directory.disabled=禁用 (总是使用游戏文件夹路径)
launcher.cache_directory.invalid=无法创建自定义的缓存文件夹,恢复默认设置
launcher.contact=联系我们
@@ -801,7 +804,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=目标版本
@@ -867,7 +871,7 @@ world.import.failed=无法导入此世界:%s
world.import.invalid=无法识别该存档压缩包
world.info.title=世界 [%s] - 世界信息
world.info.basic=基本信息
world.info.allow_cheats=允许作弊
world.info.allow_cheats=允许命令(作弊)
world.info.dimension.the_nether=下界
world.info.dimension.the_end=末地
world.info.difficulty=难度
@@ -899,7 +903,7 @@ world.name=世界名称
world.name.enter=输入世界名称
world.reveal=打开文件夹
world.show_all=显示全部
world.time=yyyy 年 MM 月 dd 日, HH:mm:ss
world.time=yyyy 年 MM 月 dd 日 HH:mm:ss
profile=游戏文件夹
profile.already_exists=该名称已存在
@@ -944,17 +948,17 @@ 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:当前游戏文件夹路径;\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,将会存在本环境变量。
\ · $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=不检查 JVM 与游戏的兼容性
settings.advanced.dont_patch_natives=不尝试自动替换本地库
@@ -1055,7 +1059,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=标题栏透明
@@ -1158,6 +1162,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=完成
File diff suppressed because it is too large. Load diff
+337 -57
View File
@@ -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;;
+212 -36
View File
@@ -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": [
@@ -1,4 +1,4 @@
package moe.mickey.minecraft.skin.fx;
package org.jackhuang.hmcl.ui.skin;
import javafx.application.Platform;
import javafx.scene.Scene;
@@ -8,7 +8,7 @@ import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.input.TransferMode;
import javafx.stage.Stage;
import moe.mickey.minecraft.skin.fx.test.Test;
import org.jackhuang.hmcl.ui.skin.test.Test;
import java.io.File;
import java.io.FileInputStream;
@@ -1,13 +1,13 @@
package moe.mickey.minecraft.skin.fx.test;
package org.jackhuang.hmcl.ui.skin.test;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
import moe.mickey.minecraft.skin.fx.FunctionHelper;
import moe.mickey.minecraft.skin.fx.SkinCanvas;
import moe.mickey.minecraft.skin.fx.SkinCanvasSupport;
import moe.mickey.minecraft.skin.fx.animation.SkinAniRunning;
import moe.mickey.minecraft.skin.fx.animation.SkinAniWavingArms;
import org.jackhuang.hmcl.ui.skin.FunctionHelper;
import org.jackhuang.hmcl.ui.skin.SkinCanvas;
import org.jackhuang.hmcl.ui.skin.SkinCanvasSupport;
import org.jackhuang.hmcl.ui.skin.animation.SkinAniRunning;
import org.jackhuang.hmcl.ui.skin.animation.SkinAniWavingArms;
import org.jackhuang.hmcl.game.TexturesLoader;
import java.util.function.Consumer;
+2 -2
View File
@@ -6,14 +6,14 @@ dependencies {
api("org.glavo:simple-png-javafx:0.3.0")
api("com.google.code.gson:gson:2.11.0")
api("com.moandjiezana.toml:toml4j:0.7.2")
api("org.tukaani:xz:1.9")
api("org.tukaani:xz:1.10")
api("org.hildan.fxgson:fx-gson:5.0.0")
api("org.jenkins-ci:constant-pool-scanner:1.2")
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")
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;
}
@@ -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"));
@@ -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() {
Loaded 100 of 127 files, more files were not shown because too many files have changed in this diff. Show more