diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteModRepository.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteModRepository.java index 8a15898173..bb8ef9b6da 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteModRepository.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LocalizedRemoteModRepository.java @@ -42,9 +42,9 @@ public abstract class LocalizedRemoteModRepository implements RemoteModRepositor protected abstract SortType getBackedRemoteModRepositorySortOrder(); @Override - public SearchResult search(DownloadProvider downloadProvider, String gameVersion, Category category, int pageOffset, int pageSize, String searchFilter, SortType sort, SortOrder sortOrder) throws IOException { + public SearchResult search(DownloadProvider downloadProvider, String gameVersion, Category category, int pageOffset, int pageSize, String searchFilter, SortType sort, SortOrder sortOrder, LoaderType loaderType) throws IOException { if (!StringUtils.containsChinese(searchFilter)) { - return getBackedRemoteModRepository().search(downloadProvider, gameVersion, category, pageOffset, pageSize, searchFilter, sort, sortOrder); + return getBackedRemoteModRepository().search(downloadProvider, gameVersion, category, pageOffset, pageSize, searchFilter, sort, sortOrder, loaderType); } Set englishSearchFiltersSet = new HashSet<>(INITIAL_CAPACITY); @@ -66,7 +66,7 @@ public SearchResult search(DownloadProvider downloadProvider, String gameVersion RemoteMod[] searchResultArray = new RemoteMod[pageSize]; int totalPages, chineseIndex = 0, englishIndex = pageSize - 1; { - SearchResult searchResult = getBackedRemoteModRepository().search(downloadProvider, gameVersion, category, pageOffset, pageSize, String.join(" ", englishSearchFiltersSet), getBackedRemoteModRepositorySortOrder(), sortOrder); + SearchResult searchResult = getBackedRemoteModRepository().search(downloadProvider, gameVersion, category, pageOffset, pageSize, String.join(" ", englishSearchFiltersSet), getBackedRemoteModRepositorySortOrder(), sortOrder, loaderType); for (Iterator iterator = searchResult.getUnsortedResults().iterator(); iterator.hasNext(); ) { if (chineseIndex > englishIndex) { LOG.warning("Too many search results! Are the backed remote mod repository broken? Or are the API broken?"); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java index cc5aceeef7..9fef5618e6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java @@ -119,7 +119,7 @@ public void loadVersion(Profile profile, String version) { if (!searchInitialized) { searchInitialized = true; - search("", null, 0, "", RemoteModRepository.SortType.POPULARITY); + search("", null, 0, "", RemoteModRepository.SortType.POPULARITY, RemoteModRepository.LoaderType.ALL); } if (versionSelection) { @@ -158,7 +158,7 @@ public void selectVersion(String versionID) { FXUtils.runInFX(() -> selectedVersion.set(versionID)); } - private void search(String userGameVersion, RemoteModRepository.Category category, int pageOffset, String searchFilter, RemoteModRepository.SortType sort) { + private void search(String userGameVersion, RemoteModRepository.Category category, int pageOffset, String searchFilter, RemoteModRepository.SortType sort, RemoteModRepository.LoaderType loader) { retrySearch = null; setLoading(true); setFailed(false); @@ -174,7 +174,7 @@ private void search(String userGameVersion, RemoteModRepository.Category categor : ""; } }).thenApplyAsync( - gameVersion -> repository.search(downloadProvider, gameVersion, category, pageOffset, 50, searchFilter, sort, RemoteModRepository.SortOrder.DESC) + gameVersion -> repository.search(downloadProvider, gameVersion, category, pageOffset, 50, searchFilter, sort, RemoteModRepository.SortOrder.DESC, loader) ).whenComplete(Schedulers.javafx(), (result, exception) -> { if (searchID != currentSearchID) { return; @@ -188,7 +188,7 @@ private void search(String userGameVersion, RemoteModRepository.Category categor } else { failed.set(true); pageCount.set(-1); - retrySearch = () -> search(userGameVersion, category, pageOffset, searchFilter, sort); + retrySearch = () -> search(userGameVersion, category, pageOffset, searchFilter, sort, loader); } }).executor(true); } @@ -361,6 +361,16 @@ protected ModDownloadListPageSkin(DownloadListPage control) { sortComboBox.getSelectionModel().select(0); searchPane.addRow(rowIndex++, new Label(i18n("mods.category")), categoryStackPane, new Label(i18n("search.sort")), sortStackPane); + StackPane loaderStackPane = new StackPane(); + JFXComboBox loaderComboBox = new JFXComboBox<>(); + loaderStackPane.getChildren().setAll(loaderComboBox); + loaderComboBox.prefWidthProperty().bind(loaderStackPane.widthProperty()); + loaderComboBox.getStyleClass().add("fit-width"); + loaderComboBox.setConverter(stringConverter(loaderType -> i18n("mods.loader." + loaderType.name().toLowerCase(Locale.ROOT)))); + loaderComboBox.getItems().setAll(RemoteModRepository.LoaderType.values()); + loaderComboBox.getSelectionModel().select(0); + searchPane.addRow(rowIndex++, new Label(i18n("mods.loader")), loaderStackPane); + IntegerProperty filterID = new SimpleIntegerProperty(this, "Filter ID", 0); IntegerProperty currentFilterID = new SimpleIntegerProperty(this, "Current Filter ID", -1); EventHandler searchAction = e -> { @@ -376,7 +386,8 @@ protected ModDownloadListPageSkin(DownloadListPage control) { .orElse(null), pageOffset == -1 ? 0 : pageOffset, nameField.getText(), - sortComboBox.getSelectionModel().getSelectedItem()); + sortComboBox.getSelectionModel().getSelectedItem(), + loaderComboBox.getSelectionModel().getSelectedItem()); }; control.listenerHolder.add(FXUtils.observeWeak( @@ -386,7 +397,8 @@ protected ModDownloadListPageSkin(DownloadListPage control) { gameVersionField.getSelectionModel().selectedItemProperty(), categoryComboBox.getSelectionModel().selectedItemProperty(), nameField.textProperty(), - sortComboBox.getSelectionModel().selectedItemProperty() + sortComboBox.getSelectionModel().selectedItemProperty(), + loaderComboBox.getSelectionModel().selectedItemProperty() )); HBox actionsBox = new HBox(8); @@ -493,6 +505,7 @@ protected ModDownloadListPageSkin(DownloadListPage control) { gameVersionField.setOnAction(searchAction); categoryComboBox.setOnAction(searchAction); sortComboBox.setOnAction(searchAction); + loaderComboBox.setOnAction(searchAction); } SpinnerPane spinnerPane = new SpinnerPane(); diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 3e813e492f..b325df499c 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -1092,6 +1092,12 @@ mods.url=Official Page mods.update_modpack_mod.warning=Updating mods in a modpack can lead to irreparable results, possibly corrupting the modpack so that it cannot launch. Are you sure you want to update? mods.install=Install mods.save_as=Save As +mods.loader=Loader +mods.loader.all=All +mods.loader.fabric=Fabric +mods.loader.forge=Forge +mods.loader.neoforge=NeoForge +mods.loader.quilt=Quilt nbt.entries=%s entries nbt.open.failed=Failed to open file diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 29ce5106b6..411aae830f 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -889,6 +889,12 @@ mods.url=官方頁面 mods.update_modpack_mod.warning=更新模組包中的模組可能導致模組包損壞,使模組包無法正常啟動。該操作不可逆,確定要更新嗎? mods.install=安裝到目前實例 mods.save_as=下載到本機目錄 +mods.loader=加載器 +mods.loader.all=全部 +mods.loader.fabric=Fabric +mods.loader.forge=Forge +mods.loader.neoforge=NeoForge +mods.loader.quilt=Quilt nbt.entries=%s 個條目 nbt.open.failed=開啟檔案失敗 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 43603a3cbb..5d4d5eb748 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -899,6 +899,12 @@ mods.url=官方页面 mods.update_modpack_mod.warning=更新整合包中的模组可能导致整合包损坏,使整合包无法正常启动。该操作不可逆,确定要更新吗? mods.install=安装到当前实例 mods.save_as=下载到本地文件夹 +mods.loader=加载器 +mods.loader.all=全部 +mods.loader.fabric=Fabric +mods.loader.forge=Forge +mods.loader.neoforge=NeoForge +mods.loader.quilt=Quilt nbt.entries=%s 个条目 nbt.open.failed=打开文件失败 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteModRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteModRepository.java index 5f74ba7d49..6ea294fd8a 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteModRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteModRepository.java @@ -47,6 +47,14 @@ enum SortType { TOTAL_DOWNLOADS } + enum LoaderType { + ALL, + FORGE, + NEOFORGE, + FABRIC, + QUILT + } + enum SortOrder { ASC, DESC @@ -84,8 +92,7 @@ public int getTotalPages() { } } - SearchResult search(DownloadProvider downloadProvider, String gameVersion, @Nullable Category category, int pageOffset, int pageSize, String searchFilter, SortType sortType, SortOrder sortOrder) - throws IOException; + SearchResult search(DownloadProvider downloadProvider, String gameVersion, @Nullable Category category, int pageOffset, int pageSize, String searchFilter, SortType sortType, SortOrder sortOrder, LoaderType loaderType) throws IOException; Optional getRemoteVersionByLocalFile(LocalModFile localModFile, Path file) throws IOException; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java index 9ad325848c..b508347ee6 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java @@ -93,6 +93,22 @@ private int toModsSearchSortField(SortType sort) { } } + private int toModsSearchModloaderField(LoaderType sort) { + https://docs.curseforge.com/rest-api/#tocS_MinecraftModLoaderIndex + switch (sort) { + case FORGE: + return 1; + case FABRIC: + return 4; + case QUILT: + return 5; + case NEOFORGE: + return 6; + default: + return 0; + } + } + private String toSortOrder(SortOrder sortOrder) { // https://docs.curseforge.com/#tocS_SortOrder switch (sortOrder) { @@ -109,21 +125,28 @@ private int calculateTotalPages(Response> response, int pageSiz } @Override - public SearchResult search(DownloadProvider downloadProvider, String gameVersion, @Nullable RemoteModRepository.Category category, int pageOffset, int pageSize, String searchFilter, SortType sortType, SortOrder sortOrder) throws IOException { + public SearchResult search(DownloadProvider downloadProvider, String gameVersion, @Nullable RemoteModRepository.Category category, int pageOffset, int pageSize, String searchFilter, SortType sortType, SortOrder sortOrder, LoaderType loaderType) throws IOException { int categoryId = 0; if (category != null && category.getSelf() instanceof CurseAddon.Category) { categoryId = ((CurseAddon.Category) category.getSelf()).getId(); } - Response> response = withApiKey(HttpRequest.GET(downloadProvider.injectURL(NetworkUtils.withQuery(PREFIX + "/v1/mods/search", mapOf( - pair("gameId", "432"), - pair("classId", Integer.toString(section)), - pair("categoryId", Integer.toString(categoryId)), - pair("gameVersion", gameVersion), - pair("searchFilter", searchFilter), - pair("sortField", Integer.toString(toModsSearchSortField(sortType))), - pair("sortOrder", toSortOrder(sortOrder)), - pair("index", Integer.toString(pageOffset * pageSize)), - pair("pageSize", Integer.toString(pageSize))))))) + + Map params = new HashMap<>(Map.of( + "gameId", "432", + "classId",Integer.toString(section), + "categoryId",Integer.toString(categoryId), + "gameVersion", gameVersion, + "searchFilter", searchFilter, + "sortField", Integer.toString(toModsSearchSortField(sortType)), + "sortOrder", toSortOrder(sortOrder), + "index", Integer.toString(pageOffset * pageSize), + "pageSize", Integer.toString(pageSize))); + + if (loaderType != LoaderType.ALL) { + params.put("modLoaderTypes", String.format("[%s]", toModsSearchModloaderField(loaderType))); + } + + Response> response = withApiKey(HttpRequest.GET(downloadProvider.injectURL(NetworkUtils.withQuery(PREFIX + "/v1/mods/search", params)))) .getJson(Response.typeOf(listTypeOf(CurseAddon.class))); if (searchFilter.isEmpty()) { return new SearchResult(response.getData().stream().map(CurseAddon::toMod), calculateTotalPages(response, pageSize)); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java index 4b60bf3cdd..cd4c7c329b 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java @@ -80,15 +80,25 @@ private static String convertSortType(SortType sortType) { } @Override - public SearchResult search(DownloadProvider downloadProvider, String gameVersion, @Nullable RemoteModRepository.Category category, int pageOffset, int pageSize, String searchFilter, SortType sort, SortOrder sortOrder) throws IOException { + public SearchResult search(DownloadProvider downloadProvider, String gameVersion, @Nullable RemoteModRepository.Category category, int pageOffset, int pageSize, String searchFilter, SortType sort, SortOrder sortOrder, LoaderType loaderType) throws IOException { List> facets = new ArrayList<>(); facets.add(Collections.singletonList("project_type:" + projectType)); if (StringUtils.isNotBlank(gameVersion)) { facets.add(Collections.singletonList("versions:" + gameVersion)); } + List categorys = new ArrayList<>(); + if (category != null && StringUtils.isNotBlank(category.getId())) { - facets.add(Collections.singletonList("categories:" + category.getId())); + categorys.add(category.getId()); + } + if (loaderType != null && loaderType != LoaderType.ALL) { + categorys.add(loaderType.name()); } + + if (!categorys.isEmpty()) { + facets.add(Collections.singletonList("categories:" + String.join(",", categorys))); + } + Map query = mapOf( pair("query", searchFilter), pair("facets", JsonUtils.UGLY_GSON.toJson(facets)),