Compare commits
16
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b44404c469 | ||
|
|
0d53d521bf | ||
|
|
a9c14bfa0b | ||
|
|
6ef823b5ba | ||
|
|
d577e1fe30 | ||
|
|
db1ef60add | ||
|
|
ab6e8692ad | ||
|
|
97ada51489 | ||
|
|
8e9c2f27a1 | ||
|
|
8563db3654 | ||
|
|
2695b5c42f | ||
|
|
bed87425e7 | ||
|
|
d6ad1cbdf7 | ||
|
|
da5bbd2b22 | ||
|
|
5c726913e4 | ||
|
|
02bd9e9d2d |
No files matched your search
@@ -1,21 +0,0 @@
|
||||
name: Sync to Gitee
|
||||
|
||||
on:
|
||||
push
|
||||
|
||||
jobs:
|
||||
run:
|
||||
if: ${{ github.repository_owner == 'HMCL-dev' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Mirror GitHub to Gitee
|
||||
uses: Yikun/hub-mirror-action@v1.4
|
||||
with:
|
||||
src: github/HMCL-dev
|
||||
dst: gitee/huanghongxun
|
||||
static_list: 'HMCL'
|
||||
force_update: true
|
||||
debug: true
|
||||
dst_key: ${{ secrets.GITEE_SYNC_BOT_PRIVATE_KEY }}
|
||||
dst_token: ${{ secrets.GITEE_SYNC_BOT_TOKEN }}
|
||||
cache_path: /github/workspace/hub-mirror-cache
|
||||
@@ -0,0 +1,32 @@
|
||||
name: Mirror Repository
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
|
||||
concurrency:
|
||||
group: mirror-repository
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
mirror:
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- name: Gitee
|
||||
repo: gitee.com/huanghongxun/HMCL
|
||||
user: 'hmcl-sync'
|
||||
token: 'GITEE_SYNC_TOKEN'
|
||||
- name: CNB
|
||||
repo: cnb.cool/HMCL-dev/HMCL
|
||||
user: 'cnb'
|
||||
token: 'CNB_SYNC_TOKEN'
|
||||
name: Mirror to ${{ matrix.target.name }}
|
||||
if: ${{ github.repository == 'HMCL-dev/HMCL' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Mirror GitHub to ${{ matrix.target.name }}
|
||||
run: |
|
||||
git clone --mirror "https://github.com/$GITHUB_REPOSITORY.git" -- repo
|
||||
cd repo
|
||||
git push -f --prune "https://${{ matrix.target.user }}:${{ secrets[matrix.target.token] }}@${{ matrix.target.repo }}.git" "refs/heads/*:refs/heads/*" "refs/tags/*:refs/tags/*"
|
||||
@@ -338,7 +338,7 @@ public final class HMCLGameRepository extends DefaultGameRepository {
|
||||
GameVersionNumber versionNumber = GameVersionNumber.asGameVersion(gameVersion);
|
||||
if (versionNumber.isAprilFools()) {
|
||||
return VersionIconType.APRIL_FOOLS.getIcon();
|
||||
} else if (versionNumber instanceof GameVersionNumber.Snapshot) {
|
||||
} else if (versionNumber instanceof GameVersionNumber.LegacySnapshot) {
|
||||
return VersionIconType.COMMAND.getIcon();
|
||||
} else if (versionNumber instanceof GameVersionNumber.Old) {
|
||||
return VersionIconType.CRAFT_TABLE.getIcon();
|
||||
|
||||
@@ -20,12 +20,12 @@ package org.jackhuang.hmcl.ui.animation;
|
||||
import javafx.animation.Animation;
|
||||
import javafx.animation.Interpolator;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.CacheHint;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.TabHeader;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class TransitionPane extends StackPane {
|
||||
@@ -40,17 +40,6 @@ public class TransitionPane extends StackPane {
|
||||
return currentNode;
|
||||
}
|
||||
|
||||
public void bindTabHeader(TabHeader tabHeader) {
|
||||
this.setContent(tabHeader.getSelectionModel().getSelectedItem().getNode(), ContainerAnimations.NONE);
|
||||
FXUtils.onChange(tabHeader.getSelectionModel().selectedItemProperty(), newValue -> {
|
||||
this.setContent(newValue.getNode(),
|
||||
ContainerAnimations.SLIDE_UP_FADE_IN,
|
||||
Motion.MEDIUM4,
|
||||
Motion.EASE_IN_OUT_CUBIC_EMPHASIZED
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public final void setContent(Node newView, AnimationProducer transition) {
|
||||
setContent(newView, transition, Motion.SHORT4);
|
||||
}
|
||||
@@ -65,6 +54,7 @@ public class TransitionPane extends StackPane {
|
||||
currentNode = newView;
|
||||
|
||||
if (!AnimationUtils.isAnimationEnabled() || previousNode == null || transition == ContainerAnimations.NONE) {
|
||||
AnimationUtils.reset(newView, true);
|
||||
getChildren().setAll(newView);
|
||||
return;
|
||||
}
|
||||
@@ -74,6 +64,15 @@ public class TransitionPane extends StackPane {
|
||||
setMouseTransparent(true);
|
||||
transition.init(this, previousNode, newView);
|
||||
|
||||
CacheHint cacheHint = newView instanceof Cacheable cacheable
|
||||
? cacheable.getCacheHint(transition)
|
||||
: null;
|
||||
|
||||
if (cacheHint != null) {
|
||||
newView.setCache(true);
|
||||
newView.setCacheHint(cacheHint);
|
||||
}
|
||||
|
||||
// runLater or "init" will not work
|
||||
Platform.runLater(() -> {
|
||||
Animation newAnimation = transition.animate(
|
||||
@@ -84,6 +83,10 @@ public class TransitionPane extends StackPane {
|
||||
newAnimation.setOnFinished(e -> {
|
||||
setMouseTransparent(false);
|
||||
getChildren().remove(previousNode);
|
||||
|
||||
if (cacheHint != null) {
|
||||
newView.setCache(false);
|
||||
}
|
||||
});
|
||||
FXUtils.playAnimation(this, "transition_pane", newAnimation);
|
||||
});
|
||||
@@ -103,4 +106,14 @@ public class TransitionPane extends StackPane {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Marks a node as cacheable as a bitmap during animation.
|
||||
public interface Cacheable {
|
||||
/// @return the [cache hint][CacheHint] to use when caching this node during the given animation,
|
||||
/// or `null` to not cache it.
|
||||
default @Nullable CacheHint getCacheHint(AnimationProducer animationProducer) {
|
||||
// https://github.com/HMCL-dev/HMCL/issues/4789
|
||||
return animationProducer == ContainerAnimations.SLIDE_UP_FADE_IN ? CacheHint.SPEED : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,12 +36,24 @@ import javafx.scene.transform.Rotate;
|
||||
import javafx.scene.transform.Scale;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.Motion;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class TabHeader extends Control implements TabControl, PageAware {
|
||||
|
||||
private final TransitionPane contentPane;
|
||||
|
||||
public TabHeader(Tab<?>... tabs) {
|
||||
this(null, tabs);
|
||||
}
|
||||
|
||||
public TabHeader(@Nullable TransitionPane contentPane, Tab<?>... tabs) {
|
||||
this.contentPane = contentPane;
|
||||
|
||||
getStyleClass().setAll("tab-header");
|
||||
if (tabs != null) {
|
||||
getTabs().addAll(tabs);
|
||||
@@ -49,41 +61,48 @@ public class TabHeader extends Control implements TabControl, PageAware {
|
||||
}
|
||||
|
||||
private final ObservableList<Tab<?>> tabs = FXCollections.observableArrayList();
|
||||
private final ObjectProperty<Side> side = new SimpleObjectProperty<>(Side.TOP);
|
||||
|
||||
@Override
|
||||
public ObservableList<Tab<?>> getTabs() {
|
||||
return tabs;
|
||||
}
|
||||
|
||||
private final ObjectProperty<SingleSelectionModel<Tab<?>>> selectionModel = new SimpleObjectProperty<>(this, "selectionModel", new TabControlSelectionModel(this));
|
||||
private final SingleSelectionModel<Tab<?>> selectionModel = new TabControlSelectionModel(this);
|
||||
|
||||
public SingleSelectionModel<Tab<?>> getSelectionModel() {
|
||||
return selectionModel.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<SingleSelectionModel<Tab<?>>> selectionModelProperty() {
|
||||
return selectionModel;
|
||||
}
|
||||
|
||||
public void setSelectionModel(SingleSelectionModel<Tab<?>> selectionModel) {
|
||||
this.selectionModel.set(selectionModel);
|
||||
public void select(Tab<?> tab) {
|
||||
select(tab, true);
|
||||
}
|
||||
|
||||
public void select(Tab<?> tab) {
|
||||
public void select(Tab<?> tab, boolean playAnimation) {
|
||||
Tab<?> oldTab = getSelectionModel().getSelectedItem();
|
||||
if (oldTab != null) {
|
||||
if (oldTab.getNode() instanceof PageAware) {
|
||||
((PageAware) oldTab.getNode()).onPageHidden();
|
||||
if (oldTab.getNode() instanceof PageAware pageAware) {
|
||||
pageAware.onPageHidden();
|
||||
}
|
||||
}
|
||||
|
||||
tab.initializeIfNeeded();
|
||||
if (tab.getNode() instanceof PageAware) {
|
||||
((PageAware) tab.getNode()).onPageShown();
|
||||
if (tab.getNode() instanceof PageAware pageAware) {
|
||||
pageAware.onPageShown();
|
||||
}
|
||||
|
||||
getSelectionModel().select(tab);
|
||||
|
||||
if (contentPane != null) {
|
||||
if (playAnimation && contentPane.getCurrentNode() != null) {
|
||||
contentPane.setContent(tab.getNode(),
|
||||
ContainerAnimations.SLIDE_UP_FADE_IN,
|
||||
Motion.MEDIUM4,
|
||||
Motion.EASE_IN_OUT_CUBIC_EMPHASIZED
|
||||
);
|
||||
} else {
|
||||
contentPane.setContent(tab.getNode(), ContainerAnimations.NONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -106,12 +125,7 @@ public class TabHeader extends Control implements TabControl, PageAware {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The position to place the tabs.
|
||||
*/
|
||||
public Side getSide() {
|
||||
return side.get();
|
||||
}
|
||||
private final ObjectProperty<Side> side = new SimpleObjectProperty<>(Side.TOP);
|
||||
|
||||
/**
|
||||
* The position of the tabs.
|
||||
@@ -120,6 +134,13 @@ public class TabHeader extends Control implements TabControl, PageAware {
|
||||
return side;
|
||||
}
|
||||
|
||||
/**
|
||||
* The position to place the tabs.
|
||||
*/
|
||||
public Side getSide() {
|
||||
return side.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* The position the place the tabs in this TabHeader.
|
||||
*/
|
||||
|
||||
@@ -39,7 +39,6 @@ import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TabControl;
|
||||
import org.jackhuang.hmcl.ui.construct.TabHeader;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
@@ -99,12 +98,11 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
resourcePackTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofResourcePack((profile, version, file) -> download(profile, version, file, "resourcepacks"), true)));
|
||||
shaderTab.setNodeSupplier(loadVersionFor(() -> new DownloadListPage(ModrinthRemoteModRepository.SHADER_PACKS, (profile, version, file) -> download(profile, version, file, "shaderpacks"), true)));
|
||||
worldTab.setNodeSupplier(loadVersionFor(() -> new DownloadListPage(CurseForgeRemoteModRepository.WORLDS)));
|
||||
tab = new TabHeader(newGameTab, modpackTab, modTab, resourcePackTab, shaderTab, worldTab);
|
||||
tab = new TabHeader(transitionPane, newGameTab, modpackTab, modTab, resourcePackTab, shaderTab, worldTab);
|
||||
|
||||
Profiles.registerVersionsListener(this::loadVersions);
|
||||
|
||||
tab.select(newGameTab);
|
||||
transitionPane.bindTabHeader(tab);
|
||||
|
||||
AdvancedListBox sideBar = new AdvancedListBox()
|
||||
.startCategory(i18n("download.game").toUpperCase(Locale.ROOT))
|
||||
@@ -121,13 +119,6 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
setCenter(transitionPane);
|
||||
}
|
||||
|
||||
private void selectTabIfCurseForgeAvailable(TabControl.Tab<?> newTab) {
|
||||
if (CurseForgeRemoteModRepository.isAvailable())
|
||||
tab.select(newTab);
|
||||
else
|
||||
Controllers.dialog(i18n("download.curseforge.unavailable"));
|
||||
}
|
||||
|
||||
private static <T extends Node> Supplier<T> loadVersionFor(Supplier<T> nodeSupplier) {
|
||||
return () -> {
|
||||
T node = nodeSupplier.get();
|
||||
@@ -202,20 +193,20 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
}
|
||||
|
||||
public void showGameDownloads() {
|
||||
tab.select(newGameTab);
|
||||
tab.select(newGameTab, false);
|
||||
}
|
||||
|
||||
public void showModpackDownloads() {
|
||||
tab.select(modpackTab);
|
||||
tab.select(modpackTab, false);
|
||||
}
|
||||
|
||||
public DownloadListPage showModDownloads() {
|
||||
tab.select(modTab);
|
||||
tab.select(modTab, false);
|
||||
return modTab.getNode();
|
||||
}
|
||||
|
||||
public void showWorldDownloads() {
|
||||
tab.select(worldTab);
|
||||
tab.select(worldTab, false);
|
||||
}
|
||||
|
||||
private static final class DownloadNavigator implements Navigation {
|
||||
|
||||
@@ -233,7 +233,7 @@ public final class VersionsPage extends Control implements WizardPage, Refreshab
|
||||
}
|
||||
setGraphic(pane);
|
||||
|
||||
twoLineListItem.setTitle(I18n.getDisplaySelfVersion(remoteVersion));
|
||||
twoLineListItem.setTitle(I18n.getDisplayVersion(remoteVersion));
|
||||
if (remoteVersion.getReleaseDate() != null) {
|
||||
twoLineListItem.setSubtitle(I18n.formatDateTime(remoteVersion.getReleaseDate()));
|
||||
} else {
|
||||
|
||||
@@ -55,11 +55,10 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
|
||||
helpTab.setNodeSupplier(HelpPage::new);
|
||||
feedbackTab.setNodeSupplier(FeedbackPage::new);
|
||||
aboutTab.setNodeSupplier(AboutPage::new);
|
||||
tab = new TabHeader(gameTab, javaManagementTab, settingsTab, personalizationTab, downloadTab, helpTab, feedbackTab, aboutTab);
|
||||
tab = new TabHeader(transitionPane, gameTab, javaManagementTab, settingsTab, personalizationTab, downloadTab, helpTab, feedbackTab, aboutTab);
|
||||
|
||||
tab.select(gameTab);
|
||||
addEventHandler(Navigator.NavigationEvent.NAVIGATED, event -> gameTab.getNode().loadVersion(Profiles.getSelectedProfile(), null));
|
||||
transitionPane.bindTabHeader(tab);
|
||||
|
||||
AdvancedListBox sideBar = new AdvancedListBox()
|
||||
.addNavigationDrawerTab(tab, gameTab, i18n("settings.type.global.manage"), SVG.STADIA_CONTROLLER, SVG.STADIA_CONTROLLER_FILL)
|
||||
@@ -90,11 +89,11 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
|
||||
|
||||
public void showGameSettings(Profile profile) {
|
||||
gameTab.getNode().loadVersion(profile, null);
|
||||
tab.select(gameTab);
|
||||
tab.select(gameTab, false);
|
||||
}
|
||||
|
||||
public void showFeedback() {
|
||||
tab.select(feedbackTab);
|
||||
tab.select(feedbackTab, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -177,7 +177,10 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
downloadItem.setLeftGraphic(wrap(SVG.DOWNLOAD));
|
||||
downloadItem.setActionButtonVisible(false);
|
||||
downloadItem.setTitle(i18n("download"));
|
||||
downloadItem.setOnAction(e -> Controllers.navigate(Controllers.getDownloadPage()));
|
||||
downloadItem.setOnAction(e -> {
|
||||
Controllers.getDownloadPage().showGameDownloads();
|
||||
Controllers.navigate(Controllers.getDownloadPage());
|
||||
});
|
||||
FXUtils.installFastTooltip(downloadItem, i18n("download.hint"));
|
||||
if (AnimationUtils.isAnimationEnabled()) {
|
||||
FXUtils.prepareOnMouseEnter(downloadItem, Controllers::prepareDownloadPage);
|
||||
@@ -188,7 +191,10 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
launcherSettingsItem.setLeftGraphic(wrap(SVG.SETTINGS));
|
||||
launcherSettingsItem.setActionButtonVisible(false);
|
||||
launcherSettingsItem.setTitle(i18n("settings"));
|
||||
launcherSettingsItem.setOnAction(e -> Controllers.navigate(Controllers.getSettingsPage()));
|
||||
launcherSettingsItem.setOnAction(e -> {
|
||||
Controllers.getSettingsPage().showGameSettings(Profiles.getSelectedProfile());
|
||||
Controllers.navigate(Controllers.getSettingsPage());
|
||||
});
|
||||
if (AnimationUtils.isAnimationEnabled()) {
|
||||
FXUtils.prepareOnMouseEnter(launcherSettingsItem, Controllers::prepareSettingsPage);
|
||||
}
|
||||
|
||||
@@ -83,7 +83,6 @@ import java.util.Locale;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class TerracottaControllerPage extends StackPane {
|
||||
@@ -182,31 +181,20 @@ public class TerracottaControllerPage extends StackPane {
|
||||
download.setSubtitle(i18n("terracotta.status.uninitialized.desc"));
|
||||
download.setRightIcon(SVG.ARROW_FORWARD);
|
||||
FXUtils.onClicked(download, () -> {
|
||||
if (globalConfig().getTerracottaAgreementVersion() >= 1) {
|
||||
TerracottaState.Preparing s = TerracottaManager.install(null);
|
||||
if (s != null) {
|
||||
UI_STATE.set(s);
|
||||
}
|
||||
TerracottaState.Preparing s = TerracottaManager.install(null);
|
||||
if (s != null) {
|
||||
UI_STATE.set(s);
|
||||
}
|
||||
|
||||
if (uninitialized.hasLegacy() && I18n.isUseChinese()) {
|
||||
Object feedback = config().getShownTips().get(FEEDBACK_TIP);
|
||||
if (!(feedback instanceof Number number) || number.intValue() < 1) {
|
||||
Controllers.confirm(i18n("terracotta.feedback.desc"), i18n("terracotta.feedback.title"), () -> {
|
||||
FXUtils.openLink(TerracottaMetadata.FEEDBACK_LINK);
|
||||
config().getShownTips().put(FEEDBACK_TIP, 1);
|
||||
}, () -> {
|
||||
});
|
||||
}
|
||||
if (uninitialized.hasLegacy() && I18n.isUseChinese()) {
|
||||
Object feedback = config().getShownTips().get(FEEDBACK_TIP);
|
||||
if (!(feedback instanceof Number number) || number.intValue() < 1) {
|
||||
Controllers.confirm(i18n("terracotta.feedback.desc"), i18n("terracotta.feedback.title"), () -> {
|
||||
FXUtils.openLink(TerracottaMetadata.FEEDBACK_LINK);
|
||||
config().getShownTips().put(FEEDBACK_TIP, 1);
|
||||
}, () -> {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
Controllers.confirmWithCountdown(i18n("terracotta.confirm.desc"), i18n("terracotta.confirm.title"), 5, MessageDialogPane.MessageType.INFO, () -> {
|
||||
globalConfig().setTerracottaAgreementVersion(1);
|
||||
TerracottaState.Preparing s = TerracottaManager.install(null);
|
||||
if (s != null) {
|
||||
UI_STATE.set(s);
|
||||
}
|
||||
}, () -> {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -32,9 +32,7 @@ import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
||||
import org.jackhuang.hmcl.ui.construct.PageAware;
|
||||
import org.jackhuang.hmcl.ui.construct.TabHeader;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.ui.main.MainPage;
|
||||
@@ -42,9 +40,12 @@ import org.jackhuang.hmcl.ui.versions.Versions;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class TerracottaPage extends DecoratorAnimatedPage implements DecoratorPage, PageAware {
|
||||
private static final int TERRACOTTA_AGREEMENT_VERSION = 2;
|
||||
|
||||
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("terracotta.terracotta")));
|
||||
private final TabHeader tab;
|
||||
private final TabHeader.Tab<TerracottaControllerPage> statusPage = new TabHeader.Tab<>("statusPage");
|
||||
@@ -55,9 +56,8 @@ public class TerracottaPage extends DecoratorAnimatedPage implements DecoratorPa
|
||||
|
||||
public TerracottaPage() {
|
||||
statusPage.setNodeSupplier(TerracottaControllerPage::new);
|
||||
tab = new TabHeader(statusPage);
|
||||
tab = new TabHeader(transitionPane, statusPage);
|
||||
tab.select(statusPage);
|
||||
transitionPane.bindTabHeader(tab);
|
||||
|
||||
BorderPane left = new BorderPane();
|
||||
FXUtils.setLimitWidth(left, 200);
|
||||
@@ -94,6 +94,12 @@ public class TerracottaPage extends DecoratorAnimatedPage implements DecoratorPa
|
||||
@Override
|
||||
public void onPageShown() {
|
||||
tab.onPageShown();
|
||||
|
||||
if (globalConfig().getTerracottaAgreementVersion() < TERRACOTTA_AGREEMENT_VERSION) {
|
||||
Controllers.confirmWithCountdown(i18n("terracotta.confirm.desc"), i18n("terracotta.confirm.title"), 5, MessageDialogPane.MessageType.INFO, () -> {
|
||||
globalConfig().setTerracottaAgreementVersion(TERRACOTTA_AGREEMENT_VERSION);
|
||||
}, () -> fireEvent(new PageCloseEvent()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.task.TaskListener;
|
||||
import org.jackhuang.hmcl.ui.*;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.download.UpdateInstallerWizardProvider;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
@@ -42,7 +43,7 @@ import java.util.concurrent.CompletableFuture;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class InstallerListPage extends ListPageBase<InstallerItem> implements VersionPage.VersionLoadable {
|
||||
public class InstallerListPage extends ListPageBase<InstallerItem> implements VersionPage.VersionLoadable, TransitionPane.Cacheable {
|
||||
private Profile profile;
|
||||
private String versionId;
|
||||
private Version version;
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.ListPageBase;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.PageAware;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
@@ -49,7 +50,7 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObject> implements VersionPage.VersionLoadable, PageAware {
|
||||
public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObject> implements VersionPage.VersionLoadable, PageAware, TransitionPane.Cacheable {
|
||||
private final BooleanProperty modded = new SimpleBooleanProperty(this, "modded", false);
|
||||
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
|
||||
@@ -37,6 +37,7 @@ import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.*;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
@@ -60,7 +61,7 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class SchematicsPage extends ListPageBase<SchematicsPage.Item> implements VersionPage.VersionLoadable {
|
||||
public final class SchematicsPage extends ListPageBase<SchematicsPage.Item> implements VersionPage.VersionLoadable, TransitionPane.Cacheable {
|
||||
|
||||
private static String translateAuthorName(String author) {
|
||||
if (I18n.isUseChinese() && "hsds".equals(author)) {
|
||||
|
||||
@@ -71,13 +71,11 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
worldListTab.setNodeSupplier(loadVersionFor(WorldListPage::new));
|
||||
schematicsTab.setNodeSupplier(loadVersionFor(SchematicsPage::new));
|
||||
|
||||
tab = new TabHeader(versionSettingsTab, installerListTab, modListTab, worldListTab, schematicsTab);
|
||||
tab = new TabHeader(transitionPane, versionSettingsTab, installerListTab, modListTab, worldListTab, schematicsTab);
|
||||
tab.select(versionSettingsTab);
|
||||
|
||||
addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated);
|
||||
|
||||
tab.select(versionSettingsTab);
|
||||
transitionPane.bindTabHeader(tab);
|
||||
|
||||
listenerHolder.add(EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> checkSelectedVersion(), EventPriority.HIGHEST));
|
||||
}
|
||||
|
||||
@@ -107,6 +105,10 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
};
|
||||
}
|
||||
|
||||
public void showInstanceSettings() {
|
||||
tab.select(versionSettingsTab, false);
|
||||
}
|
||||
|
||||
public void setVersion(String version, Profile profile) {
|
||||
this.version.set(new Profile.ProfileVersion(profile, version));
|
||||
}
|
||||
|
||||
@@ -287,6 +287,7 @@ public final class Versions {
|
||||
|
||||
public static void modifyGameSettings(Profile profile, String version) {
|
||||
Controllers.getVersionPage().setVersion(version, profile);
|
||||
Controllers.getVersionPage().showInstanceSettings();
|
||||
// VersionPage.loadVersion will be invoked after navigation
|
||||
Controllers.navigate(Controllers.getVersionPage());
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.nio.file.*;
|
||||
@@ -246,7 +247,7 @@ public final class WorldBackupsPage extends ListPageBase<WorldBackupsPage.Backup
|
||||
item.setSubtitle(formatDateTime(skinnable.getBackupTime()) + (skinnable.count == 0 ? "" : " (" + skinnable.count + ")"));
|
||||
|
||||
if (world.getGameVersion() != null)
|
||||
item.addTag(world.getGameVersion());
|
||||
item.addTag(I18n.getDisplayVersion(world.getGameVersion()));
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -23,6 +23,7 @@ import javafx.scene.control.Skin;
|
||||
import org.jackhuang.hmcl.game.World;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardSinglePage;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@@ -41,7 +42,8 @@ public class WorldExportPage extends WizardSinglePage {
|
||||
this.world = world;
|
||||
|
||||
path.set(export.toString());
|
||||
gameVersion.set(world.getGameVersion());
|
||||
if (world.getGameVersion() != null)
|
||||
gameVersion.set(I18n.getDisplayVersion(world.getGameVersion()));
|
||||
worldName.set(world.getWorldName());
|
||||
}
|
||||
|
||||
|
||||
@@ -123,7 +123,8 @@ public final class WorldInfoPage extends SpinnerPane {
|
||||
|
||||
Label gameVersionLabel = new Label();
|
||||
FXUtils.copyOnDoubleClick(gameVersionLabel);
|
||||
gameVersionLabel.setText(world.getGameVersion());
|
||||
if (world.getGameVersion() != null)
|
||||
gameVersionLabel.setText(world.getGameVersion().toNormalizedString());
|
||||
BorderPane.setAlignment(gameVersionLabel, Pos.CENTER_RIGHT);
|
||||
gameVersionPane.setRight(gameVersionLabel);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.util.ChunkBaseApp;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
@@ -73,10 +73,10 @@ public final class WorldListItemSkin extends SkinBase<WorldListItem> {
|
||||
item.setMouseTransparent(true);
|
||||
if (world.getWorldName() != null)
|
||||
item.setTitle(parseColorEscapes(world.getWorldName()));
|
||||
item.setSubtitle(i18n("world.datetime", formatDateTime(Instant.ofEpochMilli(world.getLastPlayed())), world.getGameVersion() == null ? i18n("message.unknown") : world.getGameVersion()));
|
||||
item.setSubtitle(i18n("world.datetime", formatDateTime(Instant.ofEpochMilli(world.getLastPlayed()))));
|
||||
|
||||
if (world.getGameVersion() != null)
|
||||
item.addTag(world.getGameVersion());
|
||||
item.addTag(I18n.getDisplayVersion(world.getGameVersion()));
|
||||
if (world.isLocked())
|
||||
item.addTag(i18n("world.locked"));
|
||||
}
|
||||
@@ -127,7 +127,7 @@ public final class WorldListItemSkin extends SkinBase<WorldListItem> {
|
||||
new IconedMenuItem(SVG.FORT, i18n("world.chunkbase.nether_fortress"), () -> ChunkBaseApp.openNetherFortressFinder(world), popup)
|
||||
);
|
||||
|
||||
if (GameVersionNumber.compare(world.getGameVersion(), "1.13") >= 0) {
|
||||
if (world.getGameVersion() != null && world.getGameVersion().compareTo("1.13") >= 0) {
|
||||
popupMenu.getContent().add(new IconedMenuItem(SVG.LOCATION_CITY, i18n("world.chunkbase.end_city"),
|
||||
() -> ChunkBaseApp.openEndCityFinder(world), popup));
|
||||
}
|
||||
|
||||
@@ -27,7 +27,9 @@ import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.*;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
@@ -41,7 +43,7 @@ import java.util.stream.Stream;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class WorldListPage extends ListPageBase<WorldListItem> implements VersionPage.VersionLoadable {
|
||||
public final class WorldListPage extends ListPageBase<WorldListItem> implements VersionPage.VersionLoadable, TransitionPane.Cacheable {
|
||||
private final BooleanProperty showAll = new SimpleBooleanProperty(this, "showAll", false);
|
||||
|
||||
private Path savesDir;
|
||||
@@ -49,7 +51,7 @@ public final class WorldListPage extends ListPageBase<WorldListItem> implements
|
||||
private List<World> worlds;
|
||||
private Profile profile;
|
||||
private String id;
|
||||
private String gameVersion;
|
||||
private GameVersionNumber gameVersion;
|
||||
|
||||
public WorldListPage() {
|
||||
FXUtils.applyDragListener(this, it -> "zip".equals(FileUtils.getExtension(it)), modpacks -> {
|
||||
@@ -87,7 +89,7 @@ public final class WorldListPage extends ListPageBase<WorldListItem> implements
|
||||
return;
|
||||
|
||||
setLoading(true);
|
||||
Task.runAsync(() -> gameVersion = profile.getRepository().getGameVersion(id).orElse(null))
|
||||
Task.runAsync(() -> gameVersion = profile.getRepository().getGameVersion(id).map(GameVersionNumber::asGameVersion).orElse(null))
|
||||
.thenApplyAsync(unused -> {
|
||||
try (Stream<World> stream = World.getWorlds(savesDir)) {
|
||||
return stream.parallel().collect(Collectors.toList());
|
||||
|
||||
@@ -33,7 +33,6 @@ import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.ChunkBaseApp;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.FileChannel;
|
||||
@@ -64,15 +63,13 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco
|
||||
this.world = world;
|
||||
this.backupsDir = backupsDir;
|
||||
|
||||
this.worldInfoTab.setNodeSupplier(() -> new WorldInfoPage(this));
|
||||
this.worldBackupsTab.setNodeSupplier(() -> new WorldBackupsPage(this));
|
||||
this.datapackTab.setNodeSupplier(() -> new DatapackListPage(this));
|
||||
|
||||
this.state = new SimpleObjectProperty<>(State.fromTitle(i18n("world.manage.title", world.getWorldName())));
|
||||
this.header = new TabHeader(worldInfoTab, worldBackupsTab);
|
||||
|
||||
worldInfoTab.setNodeSupplier(() -> new WorldInfoPage(this));
|
||||
worldBackupsTab.setNodeSupplier(() -> new WorldBackupsPage(this));
|
||||
datapackTab.setNodeSupplier(() -> new DatapackListPage(this));
|
||||
|
||||
this.header = new TabHeader(transitionPane, worldInfoTab, worldBackupsTab);
|
||||
header.select(worldInfoTab);
|
||||
transitionPane.bindTabHeader(header);
|
||||
|
||||
setCenter(transitionPane);
|
||||
|
||||
@@ -86,7 +83,7 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco
|
||||
.addNavigationDrawerTab(header, worldBackupsTab, i18n("world.backup"), SVG.ARCHIVE, SVG.ARCHIVE_FILL);
|
||||
|
||||
if (world.getGameVersion() != null && // old game will not write game version to level.dat
|
||||
GameVersionNumber.asGameVersion(world.getGameVersion()).isAtLeast("1.13", "17w43a")) {
|
||||
world.getGameVersion().isAtLeast("1.13", "17w43a")) {
|
||||
header.getTabs().add(datapackTab);
|
||||
sideBar.addNavigationDrawerTab(header, datapackTab, i18n("world.datapack"), SVG.EXTENSION, SVG.EXTENSION_FILL);
|
||||
}
|
||||
@@ -104,7 +101,7 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco
|
||||
new IconedMenuItem(SVG.FORT, i18n("world.chunkbase.nether_fortress"), () -> ChunkBaseApp.openNetherFortressFinder(world), popup)
|
||||
);
|
||||
|
||||
if (GameVersionNumber.compare(world.getGameVersion(), "1.13") >= 0) {
|
||||
if (world.getGameVersion() != null && world.getGameVersion().compareTo("1.13") >= 0) {
|
||||
popupMenu.getContent().add(
|
||||
new IconedMenuItem(SVG.LOCATION_CITY, i18n("world.chunkbase.end_city"), () -> ChunkBaseApp.openEndCityFinder(world), popup));
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ public final class UpdateChecker {
|
||||
try {
|
||||
result = checkUpdate(channel, preview);
|
||||
LOG.info("Latest version (" + channel + ", preview=" + preview + ") is " + result);
|
||||
} catch (IOException e) {
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Failed to check for update", e);
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ public final class ChunkBaseApp {
|
||||
|
||||
public static boolean isSupported(@NotNull World world) {
|
||||
return world.getSeed() != null && world.getGameVersion() != null &&
|
||||
GameVersionNumber.asGameVersion(world.getGameVersion()).compareTo(MIN_GAME_VERSION) >= 0;
|
||||
world.getGameVersion().compareTo(MIN_GAME_VERSION) >= 0;
|
||||
}
|
||||
|
||||
public static ChunkBaseApp newBuilder(String app, long seed) {
|
||||
@@ -60,7 +60,7 @@ public final class ChunkBaseApp {
|
||||
assert isSupported(world);
|
||||
|
||||
newBuilder("seed-map", Objects.requireNonNull(world.getSeed()))
|
||||
.addPlatform(GameVersionNumber.asGameVersion(world.getGameVersion()), world.isLargeBiomes(), SEED_MAP_GAME_VERSIONS)
|
||||
.addPlatform(world.getGameVersion(), world.isLargeBiomes(), SEED_MAP_GAME_VERSIONS)
|
||||
.open();
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ public final class ChunkBaseApp {
|
||||
assert isSupported(world);
|
||||
|
||||
newBuilder("stronghold-finder", Objects.requireNonNull(world.getSeed()))
|
||||
.addPlatform(GameVersionNumber.asGameVersion(world.getGameVersion()), world.isLargeBiomes(), STRONGHOLD_FINDER_GAME_VERSIONS)
|
||||
.addPlatform(world.getGameVersion(), world.isLargeBiomes(), STRONGHOLD_FINDER_GAME_VERSIONS)
|
||||
.open();
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ public final class ChunkBaseApp {
|
||||
assert isSupported(world);
|
||||
|
||||
newBuilder("nether-fortress-finder", Objects.requireNonNull(world.getSeed()))
|
||||
.addPlatform(GameVersionNumber.asGameVersion(world.getGameVersion()), false, NETHER_FORTRESS_GAME_VERSIONS)
|
||||
.addPlatform(world.getGameVersion(), false, NETHER_FORTRESS_GAME_VERSIONS)
|
||||
.open();
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ public final class ChunkBaseApp {
|
||||
assert isSupported(world);
|
||||
|
||||
newBuilder("endcity-finder", Objects.requireNonNull(world.getSeed()))
|
||||
.addPlatform(GameVersionNumber.asGameVersion(world.getGameVersion()), false, END_CITY_GAME_VERSIONS)
|
||||
.addPlatform(world.getGameVersion(), false, END_CITY_GAME_VERSIONS)
|
||||
.open();
|
||||
}
|
||||
|
||||
|
||||
@@ -249,7 +249,7 @@ public final class NativePatcher {
|
||||
|
||||
if (minVersion != null) {
|
||||
if (gameVersion.compareTo(minVersion) >= 0) {
|
||||
if (maxVersion != null && gameVersion.compareTo(maxVersion) >= 0)
|
||||
if (maxVersion != null && gameVersion.compareTo(maxVersion) > 0)
|
||||
return SupportStatus.UNSUPPORTED;
|
||||
|
||||
String[] defaultGameVersions = GameVersionNumber.getDefaultGameVersions();
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.util.i18n;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.download.game.GameRemoteVersion;
|
||||
import org.jackhuang.hmcl.util.i18n.translator.Translator;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -75,7 +76,11 @@ public final class I18n {
|
||||
return getTranslator().formatSpeed(bytes);
|
||||
}
|
||||
|
||||
public static String getDisplaySelfVersion(RemoteVersion version) {
|
||||
public static String getDisplayVersion(RemoteVersion version) {
|
||||
return getTranslator().getDisplayVersion(version);
|
||||
}
|
||||
|
||||
public static String getDisplayVersion(GameVersionNumber version) {
|
||||
return getTranslator().getDisplayVersion(version);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ public final class MinecraftWiki {
|
||||
private static final Pattern SNAPSHOT_PATTERN = Pattern.compile("^[0-9]{2}w[0-9]{2}.+$");
|
||||
|
||||
public static String getWikiLink(SupportedLocale locale, GameRemoteVersion version) {
|
||||
String wikiVersion = version.getSelfVersion();
|
||||
String wikiVersion = StringUtils.removeSuffix(version.getSelfVersion(), "_unobfuscated");
|
||||
var gameVersion = GameVersionNumber.asGameVersion(wikiVersion);
|
||||
|
||||
if (locale.getLocale().getLanguage().equals("lzh")) {
|
||||
@@ -72,7 +72,7 @@ public final class MinecraftWiki {
|
||||
else if (wikiVersion.startsWith("1.0.0-rc2"))
|
||||
wikiVersion = "1.0.0-rc2";
|
||||
}
|
||||
} else if (gameVersion instanceof GameVersionNumber.Snapshot) {
|
||||
} else if (gameVersion instanceof GameVersionNumber.LegacySnapshot) {
|
||||
return locale.i18n("wiki.version.game.snapshot", wikiVersion) + variantSuffix;
|
||||
} else {
|
||||
if (wikiVersion.length() >= 6 && wikiVersion.charAt(2) == 'w') {
|
||||
@@ -80,8 +80,6 @@ public final class MinecraftWiki {
|
||||
if (SNAPSHOT_PATTERN.matcher(wikiVersion).matches()) {
|
||||
if (wikiVersion.equals("22w13oneblockatatime"))
|
||||
wikiVersion = "22w13oneBlockAtATime";
|
||||
else
|
||||
wikiVersion = StringUtils.removeSuffix(wikiVersion, "_unobfuscated");
|
||||
return locale.i18n("wiki.version.game.snapshot", wikiVersion) + variantSuffix;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.util.i18n.translator;
|
||||
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.util.i18n.SupportedLocale;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
@@ -47,6 +48,10 @@ public class Translator {
|
||||
return remoteVersion.getSelfVersion();
|
||||
}
|
||||
|
||||
public String getDisplayVersion(GameVersionNumber versionNumber) {
|
||||
return versionNumber.toNormalizedString();
|
||||
}
|
||||
|
||||
/// @see [#formatDateTime(TemporalAccessor)]
|
||||
protected DateTimeFormatter dateTimeFormatter;
|
||||
|
||||
|
||||
@@ -92,22 +92,38 @@ public class Translator_lzh extends Translator {
|
||||
appendDigitByDigit(builder, String.valueOf(release.getPatch()));
|
||||
}
|
||||
|
||||
//noinspection StatementWithEmptyBody
|
||||
if (release.getEaType() == GameVersionNumber.Release.TYPE_GA) {
|
||||
// do nothing
|
||||
} else if (release.getEaType() == GameVersionNumber.Release.TYPE_PRE) {
|
||||
builder.append("之預");
|
||||
appendDigitByDigit(builder, release.getEaVersion().toString());
|
||||
} else if (release.getEaType() == GameVersionNumber.Release.TYPE_RC) {
|
||||
builder.append("之候");
|
||||
appendDigitByDigit(builder, release.getEaVersion().toString());
|
||||
} else {
|
||||
// Unsupported
|
||||
return gameVersion.toString();
|
||||
switch (release.getEaType()) {
|
||||
case GA -> {
|
||||
// do nothing
|
||||
}
|
||||
case PRE_RELEASE -> {
|
||||
builder.append("之預");
|
||||
appendDigitByDigit(builder, release.getEaVersion().toString());
|
||||
}
|
||||
case RELEASE_CANDIDATE -> {
|
||||
builder.append("之候");
|
||||
appendDigitByDigit(builder, release.getEaVersion().toString());
|
||||
}
|
||||
default -> {
|
||||
// Unsupported
|
||||
return gameVersion.toString();
|
||||
}
|
||||
}
|
||||
|
||||
switch (release.getAdditional()) {
|
||||
case NONE -> {
|
||||
}
|
||||
case UNOBFUSCATED -> {
|
||||
builder.append("涇渭");
|
||||
}
|
||||
default -> {
|
||||
// Unsupported
|
||||
return gameVersion.toString();
|
||||
}
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
} else if (gameVersion instanceof GameVersionNumber.Snapshot snapshot) {
|
||||
} else if (gameVersion instanceof GameVersionNumber.LegacySnapshot snapshot) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
appendDigitByDigit(builder, String.valueOf(snapshot.getYear()));
|
||||
@@ -120,6 +136,9 @@ public class Translator_lzh extends Translator {
|
||||
else
|
||||
builder.append(suffix);
|
||||
|
||||
if (snapshot.isUnobfuscated())
|
||||
builder.append("涇渭");
|
||||
|
||||
return builder.toString();
|
||||
} else if (gameVersion instanceof GameVersionNumber.Special) {
|
||||
String version = gameVersion.toString();
|
||||
@@ -130,6 +149,7 @@ public class Translator_lzh extends Translator {
|
||||
case "2.0_purple" -> "二點〇紫";
|
||||
case "1.rv-pre1" -> "一點真視之預一";
|
||||
case "3d shareware v1.34" -> "躍然享件一點三四";
|
||||
case "13w12~" -> "一三週一二閏";
|
||||
case "20w14infinite", "20w14~", "20w14∞" -> "二〇週一四宇";
|
||||
case "22w13oneblockatatime" -> "二二週一三典";
|
||||
case "23w13a_or_b" -> "二三週一三暨";
|
||||
@@ -179,6 +199,11 @@ public class Translator_lzh extends Translator {
|
||||
return translateGenericVersion(remoteVersion.getSelfVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayVersion(GameVersionNumber versionNumber) {
|
||||
return translateGameVersion(versionNumber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String formatDateTime(TemporalAccessor time) {
|
||||
LocalDateTime localDateTime;
|
||||
|
||||
@@ -37,6 +37,8 @@ case "$(uname -m)" in
|
||||
_HMCL_ARCH="riscv64";;
|
||||
loongarch64)
|
||||
_HMCL_ARCH="loongarch64";;
|
||||
mips64)
|
||||
_HMCL_ARCH="mips64el";;
|
||||
*)
|
||||
_HMCL_ARCH="unknown";;
|
||||
esac
|
||||
|
||||
@@ -1441,8 +1441,8 @@ terracotta.status.uninitialized.update.title=Update Multiplayer Core (~ 8MiB)
|
||||
terracotta.status.uninitialized.desc=You legally promise to strictly abide by all laws and regulations of your country or region during the multiplayer process.
|
||||
terracotta.confirm.title=User Notice
|
||||
terracotta.confirm.desc=Terracotta is a third-party open source free software. Please provide feedback on any issues you encounter through the relevant channels during use.\n\
|
||||
Terracotta is based on P2P, so that the final experience depends greatly on your network condition.\n\
|
||||
You promise to strictly abide by all laws and regulations in your country or region when playing online.
|
||||
Terracotta uses P2P technology. After a successful connection, users in the room will connect directly to each other, and no third-party server will relay your traffic. The final multiplayer experience largely depends on the network conditions of the participants.\n\
|
||||
During the entire multiplayer process, you must strictly comply with all laws and regulations of your country and region.
|
||||
terracotta.status.preparing=Multiplayer Core: Downloading (DO NOT exit HMCL)
|
||||
terracotta.status.launching=Multiplayer Core: Initializing
|
||||
terracotta.status.unknown=Multiplayer Core: Initializing
|
||||
|
||||
@@ -1229,8 +1229,8 @@ terracotta.status.uninitialized.update.title=更新聯機核心 (約 8MiB)
|
||||
terracotta.status.uninitialized.desc=您承諾,在多人聯機全過程中,您將嚴格遵守您所在國家或地區的全部法律法規
|
||||
terracotta.confirm.title=使用者須知
|
||||
terracotta.confirm.desc=陶瓦聯機是第三方開源自由軟體,您在使用過程中所遇到的問題請通過相關管道進行回饋。\n\
|
||||
多人連線基於 p2p,最終線上體驗和您的網路情況有較大關係。\n\
|
||||
您承諾,在多人連線全過程中,您將嚴格遵守您所在國家或地區的全部法律法規。
|
||||
陶瓦聯機使用 P2P 技術,聯機成功後房間內用戶之間將直接連接,不會使用第三方伺服器對您的流量進行轉發。最終聯機體驗和參與聯機者的網路情況有較大關係。\n\
|
||||
在多人聯機全過程中,您必須嚴格遵守您所在國家與地區的全部法律法規。
|
||||
terracotta.status.preparing=正在下載聯機核心 (請勿退出啟動器)
|
||||
terracotta.status.launching=正在初始化聯機核心
|
||||
terracotta.status.unknown=正在初始化聯機核心
|
||||
|
||||
@@ -1239,8 +1239,8 @@ terracotta.status.uninitialized.update.title=更新联机核心 (约 8MiB)
|
||||
terracotta.status.uninitialized.desc=您承诺,在多人联机全过程中,您将严格遵守您所在国家或地区的全部法律法规
|
||||
terracotta.confirm.title=用户须知
|
||||
terracotta.confirm.desc=陶瓦联机是第三方开源自由软件,您在使用过程中所遇到的问题请通过相关渠道进行反馈。\n\
|
||||
多人联机基于 p2p,最终联机体验和您的网络情况有较大关系。\n\
|
||||
在多人联机全过程中,您将严格遵守您所在国家或地区的全部法律法规。
|
||||
陶瓦联机使用 P2P 技术,联机成功后房间内用户之间将直接连接,不会使用第三方服务器对您的流量进行转发。最终联机体验和参与联机者的网络情况有较大关系。\n\
|
||||
在多人联机全过程中,您必须严格遵守您所在国家与地区的全部法律法规。
|
||||
terracotta.status.preparing=正在下载联机核心 (请勿退出启动器)
|
||||
terracotta.status.launching=正在初始化联机核心
|
||||
terracotta.status.unknown=正在初始化联机核心
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
{
|
||||
"version_legacy": "0\\.3\\.([89]-rc\\.([0-9]|10)|10|11|12)",
|
||||
"version_legacy": "0\\.3\\.([89]-rc\\.([0-9]|10)|(1[0-3]))",
|
||||
"version_recent": [
|
||||
"0.3.12"
|
||||
"0.3.13"
|
||||
],
|
||||
"version_latest": "0.3.13",
|
||||
"version_latest": "0.3.14",
|
||||
"classifiers": {
|
||||
"freebsd-x86_64": "sha256:0713e54ee552496416bda9d9e814e33a8950ca8f321f5b3c6dd2e07e79b0e3af",
|
||||
"linux-arm64": "sha256:61affc46035337c182adeca3671b4cf4cc59c7b4e73039899f35416f7d00ad94",
|
||||
"linux-x86_64": "sha256:9399e1627b77d518950e66d944c9a4b70c20d2e13ca2c0e2fed0ded637e7ae06",
|
||||
"linux-loongarch64": "sha256:f3eb4c40dfccc25b5b355298c776abe3d399afb57a2af38803dd78089f0c182e",
|
||||
"linux-riscv64": "sha256:26f95f8b5f83746c9cf9a8362ce0ef793ede8515897a1ba15e5e6f93c3d39533",
|
||||
"macos-arm64": "sha256:35ba271c7dc924e91c2fdd8c1cabeff2ce3d060836748a7a07162b0a5900e8d5",
|
||||
"macos-arm64.pkg": "sha256:90a613ec69f28713fe06188247c57b7cc91743c95112de5aed85ea252103beaa",
|
||||
"macos-x86_64": "sha256:45b420b15a32d5450794a9776cf45a217871cf4333b29b65a35d7358c806b5b1",
|
||||
"macos-x86_64.pkg": "sha256:587623becb3593ccb5fe542a201a67ab3a4029dfa847fcef758faff7ba6d38d5",
|
||||
"windows-arm64.exe": "sha256:bd4e1acf2f304761cdabddd9ade94d046534f4c024bc3026ac98e6be58c2bc22",
|
||||
"windows-x86_64.exe": "sha256:b89599bbcc92b00222cfc6f2e5ef636b7daf192c96efba1049a892e6cb59ee70"
|
||||
"freebsd-x86_64": "sha256:ff8c50b701e55fde9701a1f7fbe9d7317b24087c28ca6337ba9d695c53ad499a",
|
||||
"linux-arm64": "sha256:6912a14255cdeedee512244c686dd7f80dcc45d8dfbe129ea4ea3f1b22d3de60",
|
||||
"linux-x86_64": "sha256:9b13f51dc41c5112f2ab163df5c0dccdff3d22deb793a6698c013e9c09badd00",
|
||||
"linux-loongarch64": "sha256:729ca24ef643547b6a3917520ec3335b92e226ad1884c85d6bb6c2cfa917cb03",
|
||||
"linux-riscv64": "sha256:afbd8cffeacc47431f18c3ae659f7a308e1d3f2efab0ab9c5ece5176d068bf30",
|
||||
"macos-arm64": "sha256:9ed9c7a35b01dce440135c91e4a5e617d84f6c8ba13a117d13a2796dc69584f5",
|
||||
"macos-arm64.pkg": "sha256:49dbebd608c4036c260f09d4005dedaacb26fc1582b10479ed51476baa48f4a9",
|
||||
"macos-x86_64": "sha256:40a44703237b0a194b0fb18075e3b2361ace8f4319b60269cb56c5548d2afb3a",
|
||||
"macos-x86_64.pkg": "sha256:95a9de782b9c3a352ae6ec702c2fc4ca34ba807e8e4a768e61902a0c78391332",
|
||||
"windows-arm64.exe": "sha256:8428c6bc6228213c5f645ab3dd0f3b684da515be103ee53d5bdc4adf59fc8a5f",
|
||||
"windows-x86_64.exe": "sha256:942b91888264e1c96a10b7c94bd134f4b10d3e352e3c731def0ff6a3069c8221"
|
||||
},
|
||||
"downloads": [
|
||||
"https://github.com/burningtnt/Terracotta/releases/download/v${version}/terracotta-${version}-${classifier}"
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Locale;
|
||||
|
||||
/// @author Glavo
|
||||
public final class DesktopUtils {
|
||||
private static final String[] linuxBrowsers = {
|
||||
"xdg-open",
|
||||
"google-chrome",
|
||||
"firefox",
|
||||
"microsoft-edge",
|
||||
"opera",
|
||||
"konqueror",
|
||||
"mozilla"
|
||||
};
|
||||
|
||||
public static Path which(String command) {
|
||||
String path = System.getenv("PATH");
|
||||
if (path == null)
|
||||
return null;
|
||||
|
||||
try {
|
||||
for (String item : path.split(File.pathSeparator)) {
|
||||
try {
|
||||
Path program = Paths.get(item, command);
|
||||
if (Files.isExecutable(program))
|
||||
return program.toRealPath();
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void openLink(String link) {
|
||||
String osName = System.getProperty("os.name").toLowerCase(Locale.ROOT);
|
||||
|
||||
Throwable e1 = null;
|
||||
try {
|
||||
if (osName.startsWith("windows")) {
|
||||
Runtime.getRuntime().exec(new String[]{"rundll32.exe", "url.dll,FileProtocolHandler", link});
|
||||
return;
|
||||
} else if (osName.startsWith("mac") || osName.startsWith("darwin")) {
|
||||
Runtime.getRuntime().exec(new String[]{"open", link});
|
||||
return;
|
||||
} else {
|
||||
for (String browser : linuxBrowsers) {
|
||||
Path path = which(browser);
|
||||
if (path != null) {
|
||||
try {
|
||||
Runtime.getRuntime().exec(new String[]{path.toString(), link});
|
||||
return;
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
System.err.println("No known browser found");
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
e1 = e;
|
||||
}
|
||||
|
||||
try {
|
||||
java.awt.Desktop.getDesktop().browse(new URI(link));
|
||||
} catch (Throwable e2) {
|
||||
if (e1 != null)
|
||||
e2.addSuppressed(e1);
|
||||
|
||||
e2.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
|
||||
private DesktopUtils() {
|
||||
}
|
||||
}
|
||||
@@ -19,11 +19,15 @@ package org.jackhuang.hmcl;
|
||||
|
||||
import org.jackhuang.hmcl.util.SwingUtils;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class Main {
|
||||
private static final int MINIMUM_JAVA_VERSION = 17;
|
||||
private static final String DOWNLOAD_PAGE = "https://hmcl.huangyuhui.net/download/";
|
||||
|
||||
private Main() {
|
||||
}
|
||||
@@ -73,14 +77,36 @@ public final class Main {
|
||||
}
|
||||
}
|
||||
|
||||
static void showErrorAndExit(String[] args) {
|
||||
SwingUtils.initLookAndFeel();
|
||||
|
||||
ResourceBundle resourceBundle = BootProperties.getResourceBundle();
|
||||
String errorTitle = resourceBundle.getString("boot.message.error");
|
||||
|
||||
if (args.length > 0 && args[0].equals("--apply-to")) {
|
||||
String errorMessage = resourceBundle.getString("boot.manual_update");
|
||||
System.err.println(errorMessage);
|
||||
int result = JOptionPane.showOptionDialog(null, errorMessage, errorTitle, JOptionPane.YES_NO_OPTION,
|
||||
JOptionPane.ERROR_MESSAGE, null, null, null);
|
||||
|
||||
if (result == JOptionPane.YES_OPTION) {
|
||||
System.out.println("Open " + DOWNLOAD_PAGE);
|
||||
DesktopUtils.openLink(DOWNLOAD_PAGE);
|
||||
}
|
||||
} else {
|
||||
String errorMessage = resourceBundle.getString("boot.unsupported_java_version");
|
||||
System.err.println(errorMessage);
|
||||
SwingUtils.showErrorDialog(errorMessage, errorTitle);
|
||||
}
|
||||
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Throwable {
|
||||
if (getJavaFeatureVersion(System.getProperty("java.version")) >= MINIMUM_JAVA_VERSION) {
|
||||
EntryPoint.main(args);
|
||||
} else {
|
||||
String errorMessage = BootProperties.getResourceBundle().getString("boot.unsupported_java_version");
|
||||
System.err.println(errorMessage);
|
||||
SwingUtils.showErrorDialog(errorMessage);
|
||||
System.exit(1);
|
||||
showErrorAndExit(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,14 +40,26 @@ public final class SwingUtils {
|
||||
}
|
||||
|
||||
public static void showInfoDialog(Object message) {
|
||||
JOptionPane.showMessageDialog(null, message, "Info", JOptionPane.INFORMATION_MESSAGE);
|
||||
showInfoDialog(message, "Info");
|
||||
}
|
||||
|
||||
public static void showInfoDialog(Object message, String title) {
|
||||
JOptionPane.showMessageDialog(null, message, title, JOptionPane.INFORMATION_MESSAGE);
|
||||
}
|
||||
|
||||
public static void showWarningDialog(Object message) {
|
||||
JOptionPane.showMessageDialog(null, message, "Warning", JOptionPane.WARNING_MESSAGE);
|
||||
showWarningDialog(message, "Warning");
|
||||
}
|
||||
|
||||
public static void showWarningDialog(Object message, String title) {
|
||||
JOptionPane.showMessageDialog(null, message, title, JOptionPane.WARNING_MESSAGE);
|
||||
}
|
||||
|
||||
public static void showErrorDialog(Object message) {
|
||||
JOptionPane.showMessageDialog(null, message, "Error", JOptionPane.ERROR_MESSAGE);
|
||||
showErrorDialog(message, "Error");
|
||||
}
|
||||
|
||||
public static void showErrorDialog(Object message, String title) {
|
||||
JOptionPane.showMessageDialog(null, message, title, JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
@@ -17,3 +17,6 @@
|
||||
#
|
||||
|
||||
boot.unsupported_java_version=HMCL requires Java 17 or later to run, but still supports launching games with Java 6~16. Please install the latest version of Java and try opening HMCL again.\nYou can keep your old version of Java. HMCL can detect and manage multiple Java installations, and will automatically select the appropriate Java version for your game.
|
||||
boot.manual_update=HMCL cannot complete automatic updates in the current environment. Please download the latest version of HMCL manually.\nWould you like to go to the download page?
|
||||
|
||||
boot.message.error=Error
|
||||
@@ -17,3 +17,6 @@
|
||||
#
|
||||
|
||||
boot.unsupported_java_version=HMCL 需要 Java 17 或更高版本才能运行,但依然支持使用 Java 6~16 启动游戏。请安装最新版本的 Java 再尝试启动 HMCL。\n你可以继续保留旧版本 Java。HMCL 能够识别与管理多个 Java,并会自动根据游戏版本为你选择合适的 Java。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。
|
||||
boot.manual_update=HMCL 在当前环境无法完成自动更新,请手动下载最新版本的 HMCL。\n是否前往下载页面?
|
||||
|
||||
boot.message.error=错误
|
||||
@@ -17,3 +17,6 @@
|
||||
#
|
||||
|
||||
boot.unsupported_java_version=HMCL 需要 Java 17 或更高版本才能執行,但依然支援使用 Java 6~16 啟動遊戲。請安裝最新版本的 Java 再嘗試開啟 HMCL。\n你可以繼續保留舊版本 Java。HMCL 能夠識別與管理多個 Java,並會自動根據遊戲版本為你選取合適的 Java。
|
||||
boot.manual_update=HMCL 在當前環境無法完成自動更新,請手動下載最新版本的 HMCL。\n是否前往下載頁面?
|
||||
|
||||
boot.message.error=錯誤
|
||||
@@ -82,8 +82,8 @@ public final class BMCLAPIDownloadProvider implements DownloadProvider {
|
||||
pair("https://authlib-injector.yushi.moe", apiRoot + "/mirrors/authlib-injector"),
|
||||
pair("https://repo1.maven.org/maven2", "https://mirrors.cloud.tencent.com/nexus/repository/maven-public"),
|
||||
pair("https://repo.maven.apache.org/maven2", "https://mirrors.cloud.tencent.com/nexus/repository/maven-public"),
|
||||
pair("https://hmcl-dev.github.io/metadata/cleanroom", "https://alist.8mi.tech/d/mirror/HMCL-Metadata/Auto/cleanroom"),
|
||||
pair("https://hmcl-dev.github.io/metadata/fmllibs", "https://alist.8mi.tech/d/mirror/HMCL-Metadata/Auto/fmllibs"),
|
||||
pair("https://hmcl.glavo.site/metadata/cleanroom", "https://alist.8mi.tech/d/mirror/HMCL-Metadata/Auto/cleanroom"),
|
||||
pair("https://hmcl.glavo.site/metadata/fmllibs", "https://alist.8mi.tech/d/mirror/HMCL-Metadata/Auto/fmllibs"),
|
||||
pair("https://zkitefly.github.io/unlisted-versions-of-minecraft", "https://alist.8mi.tech/d/mirror/unlisted-versions-of-minecraft/Auto")
|
||||
// // https://github.com/mcmod-info-mirror/mcim-rust-api
|
||||
// pair("https://api.modrinth.com", "https://mod.mcimirror.top/modrinth"),
|
||||
|
||||
+2
-2
@@ -27,8 +27,8 @@ import java.util.Collections;
|
||||
|
||||
public final class CleanroomVersionList extends VersionList<CleanroomRemoteVersion> {
|
||||
private final DownloadProvider downloadProvider;
|
||||
private static final String LOADER_LIST_URL = "https://hmcl-dev.github.io/metadata/cleanroom/index.json";
|
||||
private static final String INSTALLER_URL = "https://hmcl-dev.github.io/metadata/cleanroom/files/cleanroom-%s-installer.jar";
|
||||
private static final String LOADER_LIST_URL = "https://hmcl.glavo.site/metadata/cleanroom/index.json";
|
||||
private static final String INSTALLER_URL = "https://hmcl.glavo.site/metadata/cleanroom/files/cleanroom-%s-installer.jar";
|
||||
|
||||
public CleanroomVersionList(DownloadProvider downloadProvider) {
|
||||
this.downloadProvider = downloadProvider;
|
||||
|
||||
@@ -96,5 +96,5 @@ public final class ForgeVersionList extends VersionList<ForgeRemoteVersion> {
|
||||
});
|
||||
}
|
||||
|
||||
public static final URI FORGE_LIST = URI.create("https://hmcl-dev.github.io/metadata/forge/");
|
||||
public static final URI FORGE_LIST = URI.create("https://hmcl.glavo.site/metadata/forge/");
|
||||
}
|
||||
@@ -149,7 +149,7 @@ public final class GameLibrariesTask extends Task<Void> {
|
||||
Path file = libDir.resolve(fmlLib.name);
|
||||
if (shouldDownloadFMLLib(fmlLib, file)) {
|
||||
List<URI> uris = dependencyManager.getDownloadProvider()
|
||||
.injectURLWithCandidates(fmlLib.getDownloadURI());
|
||||
.injectURLWithCandidates(fmlLib.downloadUrl());
|
||||
dependencies.add(new FileDownloadTask(uris, file)
|
||||
.withCounter("hmcl.install.libraries"));
|
||||
}
|
||||
@@ -193,9 +193,12 @@ public final class GameLibrariesTask extends Task<Void> {
|
||||
if (forgeVersion.startsWith("7.8.1.")) {
|
||||
return List.of(
|
||||
new FMLLib("argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51"),
|
||||
new FMLLib("guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a"),
|
||||
new FMLLib("asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58"),
|
||||
new FMLLib("bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65"),
|
||||
new FMLLib("guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a",
|
||||
"https://repo1.maven.org/maven2/com/google/guava/guava/14.0-rc3/guava-14.0-rc3.jar"),
|
||||
new FMLLib("asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58",
|
||||
"https://repo1.maven.org/maven2/org/ow2/asm/asm-all/4.1/asm-all-4.1.jar"),
|
||||
new FMLLib("bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65",
|
||||
"https://repo1.maven.org/maven2/org/bouncycastle/bcprov-jdk15on/1.48/bcprov-jdk15on-1.48.jar"),
|
||||
new FMLLib("deobfuscation_data_1.5.2.zip", "446e55cd986582c70fcf12cb27bc00114c5adfd9"),
|
||||
new FMLLib("scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85")
|
||||
);
|
||||
@@ -204,9 +207,9 @@ public final class GameLibrariesTask extends Task<Void> {
|
||||
return null;
|
||||
}
|
||||
|
||||
private record FMLLib(String name, String sha1) {
|
||||
public String getDownloadURI() {
|
||||
return "https://hmcl-dev.github.io/metadata/fmllibs/" + name;
|
||||
private record FMLLib(String name, String sha1, String downloadUrl) {
|
||||
FMLLib(String name, String sha1) {
|
||||
this(name, sha1, "https://hmcl.glavo.site/metadata/fmllibs/" + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import com.github.steveice10.opennbt.tag.builtin.StringTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
import javafx.scene.image.Image;
|
||||
import org.jackhuang.hmcl.util.io.*;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -48,7 +49,7 @@ public final class World {
|
||||
private final Path file;
|
||||
private String fileName;
|
||||
private String worldName;
|
||||
private String gameVersion;
|
||||
private GameVersionNumber gameVersion;
|
||||
private long lastPlayed;
|
||||
private Image icon;
|
||||
private Long seed;
|
||||
@@ -108,7 +109,7 @@ public final class World {
|
||||
return lastPlayed;
|
||||
}
|
||||
|
||||
public String getGameVersion() {
|
||||
public @Nullable GameVersionNumber getGameVersion() {
|
||||
return gameVersion;
|
||||
}
|
||||
|
||||
@@ -188,7 +189,7 @@ public final class World {
|
||||
CompoundTag version = data.get("Version");
|
||||
|
||||
if (version.get("Name") instanceof StringTag)
|
||||
gameVersion = version.<StringTag>get("Name").getValue();
|
||||
gameVersion = GameVersionNumber.asGameVersion(version.<StringTag>get("Name").getValue());
|
||||
}
|
||||
|
||||
Tag worldGenSettings = data.get("WorldGenSettings");
|
||||
|
||||
@@ -187,7 +187,9 @@ public abstract class FetchTask<T> extends Task<T> {
|
||||
|
||||
ArrayList<IOException> exceptions = null;
|
||||
|
||||
for (int retryTime = 0; retryTime < retry; retryTime++) {
|
||||
// If loading the cache fails, the cache should not be loaded again.
|
||||
boolean useCachedResult = true;
|
||||
for (int retryTime = 0, retryLimit = retry; retryTime < retryLimit; retryTime++) {
|
||||
if (isCancelled()) {
|
||||
throw new InterruptedException();
|
||||
}
|
||||
@@ -204,12 +206,14 @@ public abstract class FetchTask<T> extends Task<T> {
|
||||
|
||||
LinkedHashMap<String, String> headers = new LinkedHashMap<>();
|
||||
headers.put("accept-encoding", "gzip");
|
||||
if (checkETag)
|
||||
if (useCachedResult && checkETag)
|
||||
headers.putAll(repository.injectConnection(uri));
|
||||
|
||||
do {
|
||||
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(currentURI);
|
||||
requestBuilder.timeout(Duration.ofMillis(NetworkUtils.TIME_OUT));
|
||||
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(currentURI)
|
||||
.timeout(Duration.ofMillis(NetworkUtils.TIME_OUT))
|
||||
.header("User-Agent", Holder.USER_AGENT);
|
||||
|
||||
headers.forEach(requestBuilder::header);
|
||||
response = Holder.HTTP_CLIENT.send(requestBuilder.build(), BODY_HANDLER);
|
||||
|
||||
@@ -248,7 +252,7 @@ public abstract class FetchTask<T> extends Task<T> {
|
||||
} while (true);
|
||||
|
||||
int responseCode = response.statusCode();
|
||||
if (responseCode == HttpURLConnection.HTTP_NOT_MODIFIED) {
|
||||
if (useCachedResult && responseCode == HttpURLConnection.HTTP_NOT_MODIFIED) {
|
||||
// Handle cache
|
||||
try {
|
||||
Path cache = repository.getCachedRemoteFile(currentURI, false);
|
||||
@@ -260,9 +264,10 @@ public abstract class FetchTask<T> extends Task<T> {
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Unable to use cached file, redownload " + NetworkUtils.dropQuery(uri), e);
|
||||
repository.removeRemoteEntry(currentURI);
|
||||
useCachedResult = false;
|
||||
// Now we must reconnect the server since 304 may result in empty content,
|
||||
// if we want to redownload the file, we must reconnect the server without etag settings.
|
||||
retryTime--;
|
||||
retryLimit++;
|
||||
continue;
|
||||
}
|
||||
} else if (responseCode / 100 == 4) {
|
||||
@@ -503,6 +508,7 @@ public abstract class FetchTask<T> extends Task<T> {
|
||||
/// Ensure that [#HTTP_CLIENT] is initialized after ProxyManager has been initialized.
|
||||
private static final class Holder {
|
||||
private static final HttpClient HTTP_CLIENT;
|
||||
private static final String USER_AGENT = System.getProperty("http.agent", "HMCL");
|
||||
|
||||
static {
|
||||
boolean useHttp2 = !"false".equalsIgnoreCase(System.getProperty("hmcl.http2"));
|
||||
|
||||
@@ -193,7 +193,7 @@ public class FileDownloadTask extends FetchTask<Void> {
|
||||
if (integrityCheck != null) {
|
||||
algorithm = integrityCheck.getAlgorithm();
|
||||
checksum = integrityCheck.getChecksum();
|
||||
} else if (bmclapiHash != null) {
|
||||
} else if (bmclapiHash != null && DigestUtils.isSha1Digest(bmclapiHash)) {
|
||||
algorithm = "SHA-1";
|
||||
checksum = bmclapiHash;
|
||||
} else {
|
||||
|
||||
@@ -113,16 +113,19 @@ public final class ManagedProcess {
|
||||
*/
|
||||
public List<String> getLines(Predicate<String> lineFilter) {
|
||||
lock.lock();
|
||||
try {
|
||||
if (lineFilter == null)
|
||||
return List.copyOf(lines);
|
||||
|
||||
if (lineFilter == null)
|
||||
return List.copyOf(lines);
|
||||
|
||||
ArrayList<String> res = new ArrayList<>();
|
||||
for (String line : this.lines) {
|
||||
if (lineFilter.test(line))
|
||||
res.add(line);
|
||||
ArrayList<String> res = new ArrayList<>();
|
||||
for (String line : this.lines) {
|
||||
if (lineFilter.test(line))
|
||||
res.add(line);
|
||||
}
|
||||
return Collections.unmodifiableList(res);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
return Collections.unmodifiableList(res);
|
||||
}
|
||||
|
||||
public void addLine(String line) {
|
||||
|
||||
+323
-141
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util.versioning;
|
||||
|
||||
import org.intellij.lang.annotations.MagicConstant;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
@@ -28,6 +28,8 @@ import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
@@ -38,6 +40,10 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
|
||||
}
|
||||
|
||||
public static GameVersionNumber asGameVersion(String version) {
|
||||
GameVersionNumber versionNumber = Versions.SPECIALS.get(version);
|
||||
if (versionNumber != null)
|
||||
return versionNumber;
|
||||
|
||||
try {
|
||||
if (!version.isEmpty()) {
|
||||
char ch = version.charAt(0);
|
||||
@@ -53,20 +59,15 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
|
||||
if (version.equals("0.0"))
|
||||
return Release.ZERO;
|
||||
|
||||
if (version.startsWith("1."))
|
||||
return Release.parse(version);
|
||||
if (version.length() >= 6 && version.charAt(2) == 'w')
|
||||
return LegacySnapshot.parse(version);
|
||||
|
||||
if (version.length() == 6 && version.charAt(2) == 'w')
|
||||
return Snapshot.parse(version);
|
||||
return Release.parse(version);
|
||||
}
|
||||
} catch (IllegalArgumentException ignore) {
|
||||
} catch (Throwable ignore) {
|
||||
}
|
||||
|
||||
Special special = Versions.SPECIALS.get(version);
|
||||
if (special == null) {
|
||||
special = new Special(version);
|
||||
}
|
||||
return special;
|
||||
return new Special(version, version);
|
||||
}
|
||||
|
||||
public static GameVersionNumber asGameVersion(Optional<String> version) {
|
||||
@@ -94,17 +95,22 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
|
||||
}
|
||||
|
||||
final String value;
|
||||
final String normalized;
|
||||
|
||||
GameVersionNumber(String value) {
|
||||
GameVersionNumber(String value, String normalized) {
|
||||
this.value = value;
|
||||
this.normalized = normalized;
|
||||
}
|
||||
|
||||
public boolean isAprilFools() {
|
||||
if (this instanceof Special && !value.endsWith("_unobfuscated"))
|
||||
return true;
|
||||
if (this instanceof Special) {
|
||||
String normalizedVersion = this.toNormalizedString();
|
||||
return !normalizedVersion.startsWith("1.") && !normalizedVersion.equals("13w12~")
|
||||
|| normalizedVersion.equals("1.RV-Pre1");
|
||||
}
|
||||
|
||||
if (this instanceof Snapshot snapshot) {
|
||||
return snapshot.intValue == Snapshot.toInt(15, 14, 'a');
|
||||
if (this instanceof LegacySnapshot snapshot) {
|
||||
return snapshot.intValue == LegacySnapshot.toInt(15, 14, 'a', false);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -144,10 +150,10 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
|
||||
///
|
||||
/// ```java
|
||||
/// GameVersionNumber.asVersion("...").isAtLeast("1.13", "17w43a");
|
||||
/// ```
|
||||
///```
|
||||
///
|
||||
/// @param strictReleaseVersion When `strictReleaseVersion` is `false`, `releaseVersion` is considered less than
|
||||
/// its corresponding pre/rc versions.
|
||||
/// its corresponding pre/rc versions.
|
||||
public boolean isAtLeast(@NotNull String releaseVersion, @NotNull String snapshotVersion, boolean strictReleaseVersion) {
|
||||
if (this instanceof Release self) {
|
||||
Release other;
|
||||
@@ -159,17 +165,35 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
|
||||
|
||||
return self.compareToRelease(other) >= 0;
|
||||
} else {
|
||||
return this.compareTo(Snapshot.parse(snapshotVersion)) >= 0;
|
||||
return this.compareTo(LegacySnapshot.parse(snapshotVersion)) >= 0;
|
||||
}
|
||||
}
|
||||
|
||||
public String toNormalizedString() {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return value;
|
||||
}
|
||||
|
||||
protected ToStringBuilder buildDebugString() {
|
||||
return new ToStringBuilder(this)
|
||||
.append("value", value)
|
||||
.append("normalized", normalized)
|
||||
.append("type", getType());
|
||||
}
|
||||
|
||||
public final String toDebugString() {
|
||||
return buildDebugString().toString();
|
||||
}
|
||||
|
||||
public static final class Old extends GameVersionNumber {
|
||||
static Old parse(String value) {
|
||||
if (value.isEmpty())
|
||||
throw new IllegalArgumentException("Empty old version number");
|
||||
|
||||
Type type;
|
||||
int prefixLength = 1;
|
||||
switch (value.charAt(0)) {
|
||||
@@ -215,7 +239,7 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
|
||||
final VersionNumber versionNumber;
|
||||
|
||||
private Old(String value, Type type, VersionNumber versionNumber) {
|
||||
super(value);
|
||||
super(value, value);
|
||||
this.type = type;
|
||||
this.versionNumber = versionNumber;
|
||||
}
|
||||
@@ -231,83 +255,141 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
return o instanceof Old other && type == other.type && this.versionNumber.compareTo(other.versionNumber) == 0;
|
||||
public int hashCode() {
|
||||
return Objects.hash(type, versionNumber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(type, versionNumber.hashCode());
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof Old that
|
||||
&& this.type == that.type
|
||||
&& this.versionNumber.equals(that.versionNumber);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Release extends GameVersionNumber {
|
||||
private static final int MINIMUM_YEAR_MAJOR_VERSION = 25;
|
||||
|
||||
private static final Pattern PATTERN = Pattern.compile("1\\.(?<minor>[0-9]+)(\\.(?<patch>[0-9]+))?((?<eaType>(-[a-zA-Z]+| Pre-Release ))(?<eaVersion>.+))?");
|
||||
public enum ReleaseType {
|
||||
UNKNOWN(""),
|
||||
SNAPSHOT("-snapshot-"),
|
||||
PRE_RELEASE("-pre"),
|
||||
RELEASE_CANDIDATE("-rc"),
|
||||
GA("");
|
||||
private final String infix;
|
||||
|
||||
public static final int TYPE_GA = Integer.MAX_VALUE;
|
||||
ReleaseType(String infix) {
|
||||
this.infix = infix;
|
||||
}
|
||||
}
|
||||
|
||||
public static final int TYPE_UNKNOWN = 0;
|
||||
public static final int TYPE_EXP = 1;
|
||||
public static final int TYPE_PRE = 2;
|
||||
public static final int TYPE_RC = 3;
|
||||
public enum Additional {
|
||||
NONE(""), UNOBFUSCATED("_unobfuscated");
|
||||
private final String suffix;
|
||||
|
||||
static final Release ZERO = new Release("0.0", 0, 0, 0, TYPE_GA, VersionNumber.ZERO);
|
||||
Additional(String suffix) {
|
||||
this.suffix = suffix;
|
||||
}
|
||||
}
|
||||
|
||||
static final Release ZERO = new Release(
|
||||
"0.0", "0.0",
|
||||
0, 0, 0,
|
||||
ReleaseType.UNKNOWN, VersionNumber.ZERO, Additional.NONE
|
||||
);
|
||||
|
||||
private static final Pattern VERSION_PATTERN = Pattern.compile("(?<prefix>(?<major>1|[1-9]\\d+)\\.(?<minor>\\d+)(\\.(?<patch>[0-9]+))?)(?<suffix>.*)");
|
||||
|
||||
static Release parse(String value) {
|
||||
Matcher matcher = PATTERN.matcher(value);
|
||||
Matcher matcher = VERSION_PATTERN.matcher(value);
|
||||
if (!matcher.matches()) {
|
||||
throw new IllegalArgumentException(value);
|
||||
}
|
||||
|
||||
int major = Integer.parseInt(matcher.group("major"));
|
||||
if (major != 1 && major < MINIMUM_YEAR_MAJOR_VERSION)
|
||||
throw new IllegalArgumentException(value);
|
||||
|
||||
int minor = Integer.parseInt(matcher.group("minor"));
|
||||
|
||||
String patchString = matcher.group("patch");
|
||||
int patch = patchString != null ? Integer.parseInt(patchString) : 0;
|
||||
|
||||
String eaTypeString = matcher.group("eaType");
|
||||
int eaType;
|
||||
if (eaTypeString == null) {
|
||||
eaType = TYPE_GA;
|
||||
} else if ("-pre".equals(eaTypeString) || " Pre-Release ".equals(eaTypeString)) {
|
||||
eaType = TYPE_PRE;
|
||||
} else if ("-rc".equals(eaTypeString)) {
|
||||
eaType = TYPE_RC;
|
||||
} else if ("-exp".equals(eaTypeString)) {
|
||||
eaType = TYPE_EXP;
|
||||
String suffix = matcher.group("suffix");
|
||||
|
||||
ReleaseType releaseType;
|
||||
VersionNumber eaVersion;
|
||||
Additional additional = Additional.NONE;
|
||||
boolean needNormalize = false;
|
||||
|
||||
if (suffix.endsWith("_unobfuscated")) {
|
||||
suffix = suffix.substring(0, suffix.length() - "_unobfuscated".length());
|
||||
additional = Additional.UNOBFUSCATED;
|
||||
} else if (suffix.endsWith(" Unobfuscated")) {
|
||||
needNormalize = true;
|
||||
suffix = suffix.substring(0, suffix.length() - " Unobfuscated".length());
|
||||
additional = Additional.UNOBFUSCATED;
|
||||
}
|
||||
|
||||
if (suffix.isEmpty()) {
|
||||
releaseType = ReleaseType.GA;
|
||||
eaVersion = VersionNumber.ZERO;
|
||||
} else if (suffix.startsWith("-snapshot-")) {
|
||||
releaseType = ReleaseType.SNAPSHOT;
|
||||
eaVersion = VersionNumber.asVersion(suffix.substring("-snapshot-".length()));
|
||||
} else if (suffix.startsWith(" Snapshot ")) {
|
||||
needNormalize = true;
|
||||
releaseType = ReleaseType.SNAPSHOT;
|
||||
eaVersion = VersionNumber.asVersion(suffix.substring(" Snapshot ".length()));
|
||||
} else if (suffix.startsWith("-pre")) {
|
||||
releaseType = ReleaseType.PRE_RELEASE;
|
||||
eaVersion = VersionNumber.asVersion(suffix.substring("-pre".length()));
|
||||
} else if (suffix.startsWith(" Pre-Release ")) {
|
||||
needNormalize = true;
|
||||
releaseType = ReleaseType.PRE_RELEASE;
|
||||
eaVersion = VersionNumber.asVersion(suffix.substring(" Pre-Release ".length()));
|
||||
} else if (suffix.startsWith("-rc")) {
|
||||
releaseType = ReleaseType.RELEASE_CANDIDATE;
|
||||
eaVersion = VersionNumber.asVersion(suffix.substring("-rc".length()));
|
||||
} else if (suffix.startsWith(" Release Candidate ")) {
|
||||
needNormalize = true;
|
||||
releaseType = ReleaseType.RELEASE_CANDIDATE;
|
||||
eaVersion = VersionNumber.asVersion(suffix.substring(" Release Candidate ".length()));
|
||||
} else {
|
||||
eaType = TYPE_UNKNOWN;
|
||||
throw new IllegalArgumentException(value);
|
||||
}
|
||||
|
||||
String eaVersionString = matcher.group("eaVersion");
|
||||
VersionNumber eaVersion = eaVersionString != null ? VersionNumber.asVersion(eaVersionString) : VersionNumber.ZERO;
|
||||
|
||||
return new Release(value, 1, minor, patch, eaType, eaVersion);
|
||||
}
|
||||
|
||||
private static int getNumberLength(String value, int offset) {
|
||||
int current = offset;
|
||||
while (current < value.length()) {
|
||||
char ch = value.charAt(current);
|
||||
if (ch < '0' || ch > '9')
|
||||
break;
|
||||
|
||||
current++;
|
||||
String normalized;
|
||||
if (needNormalize) {
|
||||
StringBuilder builder = new StringBuilder(value.length());
|
||||
builder.append(matcher.group("prefix"));
|
||||
if (releaseType != ReleaseType.GA) {
|
||||
builder.append(releaseType.infix);
|
||||
builder.append(eaVersion);
|
||||
}
|
||||
builder.append(additional.suffix);
|
||||
normalized = builder.toString();
|
||||
} else {
|
||||
normalized = value;
|
||||
}
|
||||
|
||||
return current - offset;
|
||||
return new Release(value, normalized, major, minor, patch, releaseType, eaVersion, additional);
|
||||
}
|
||||
|
||||
/// Quickly parses a simple format (`1\.[0-9]+(\.[0-9]+)?`) release version.
|
||||
/// The returned [#eaType] will be set to [#TYPE_UNKNOWN], meaning it will be less than all pre/rc and official versions of this version.
|
||||
/// Quickly parses a simple format (`[1-9][0-9]+\.[0-9]+(\.[0-9]+)?`) release version.
|
||||
/// The returned [#eaType] will be set to [ReleaseType#UNKNOWN], meaning it will be less than all pre/rc and official versions of this version.
|
||||
///
|
||||
/// @see GameVersionNumber#isAtLeast(String, String)
|
||||
static Release parseSimple(String value) {
|
||||
if (!value.startsWith("1."))
|
||||
int majorLength = getNumberLength(value, 0);
|
||||
if (majorLength == 0 || value.length() < majorLength + 2 || value.charAt(majorLength) != '.')
|
||||
throw new IllegalArgumentException(value);
|
||||
|
||||
final int minorOffset = 2;
|
||||
int major = Integer.parseInt(value.substring(0, majorLength));
|
||||
if (major != 1 && major < MINIMUM_YEAR_MAJOR_VERSION)
|
||||
throw new IllegalArgumentException(value);
|
||||
|
||||
final int minorOffset = majorLength + 1;
|
||||
|
||||
int minorLength = getNumberLength(value, minorOffset);
|
||||
if (minorLength == 0)
|
||||
@@ -326,27 +408,41 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
|
||||
patch = Integer.parseInt(value.substring(patchOffset));
|
||||
}
|
||||
|
||||
return new Release(value, 1, minor, patch, TYPE_UNKNOWN, VersionNumber.ZERO);
|
||||
return new Release(value, value, major, minor, patch, ReleaseType.UNKNOWN, VersionNumber.ZERO, Additional.NONE);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException(value);
|
||||
}
|
||||
}
|
||||
|
||||
private static int getNumberLength(String value, int offset) {
|
||||
int current = offset;
|
||||
while (current < value.length()) {
|
||||
char ch = value.charAt(current);
|
||||
if (ch < '0' || ch > '9')
|
||||
break;
|
||||
|
||||
current++;
|
||||
}
|
||||
|
||||
return current - offset;
|
||||
}
|
||||
|
||||
private final int major;
|
||||
private final int minor;
|
||||
private final int patch;
|
||||
|
||||
@MagicConstant(intValues = {TYPE_GA, TYPE_UNKNOWN, TYPE_EXP, TYPE_PRE, TYPE_RC})
|
||||
private final int eaType;
|
||||
private final ReleaseType eaType;
|
||||
private final VersionNumber eaVersion;
|
||||
private final Additional additional;
|
||||
|
||||
Release(String value, int major, int minor, int patch, int eaType, VersionNumber eaVersion) {
|
||||
super(value);
|
||||
Release(String value, String normalized, int major, int minor, int patch, ReleaseType eaType, VersionNumber eaVersion, Additional additional) {
|
||||
super(value, normalized);
|
||||
this.major = major;
|
||||
this.minor = minor;
|
||||
this.patch = patch;
|
||||
this.eaType = eaType;
|
||||
this.eaVersion = eaVersion;
|
||||
this.additional = additional;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -367,35 +463,45 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
|
||||
if (c != 0)
|
||||
return c;
|
||||
|
||||
c = Integer.compare(this.eaType, other.eaType);
|
||||
c = this.eaType.compareTo(other.eaType);
|
||||
if (c != 0)
|
||||
return c;
|
||||
|
||||
return this.eaVersion.compareTo(other.eaVersion);
|
||||
c = this.eaVersion.compareTo(other.eaVersion);
|
||||
if (c != 0)
|
||||
return c;
|
||||
|
||||
return this.additional.compareTo(other.additional);
|
||||
}
|
||||
|
||||
int compareToSnapshot(Snapshot other) {
|
||||
int idx = Arrays.binarySearch(Versions.SNAPSHOT_INTS, other.intValue);
|
||||
if (idx >= 0)
|
||||
return this.compareToRelease(Versions.SNAPSHOT_PREV[idx]) <= 0 ? -1 : 1;
|
||||
|
||||
idx = -(idx + 1);
|
||||
if (idx == Versions.SNAPSHOT_INTS.length)
|
||||
int compareToSnapshot(LegacySnapshot other) {
|
||||
if (major == 0) {
|
||||
return -1;
|
||||
} else if (major == 1) {
|
||||
int idx = Arrays.binarySearch(Versions.SNAPSHOT_INTS, other.intValue);
|
||||
if (idx >= 0)
|
||||
return this.compareToRelease(Versions.SNAPSHOT_PREV[idx]) <= 0 ? -1 : 1;
|
||||
|
||||
return this.compareToRelease(Versions.SNAPSHOT_PREV[idx]) <= 0 ? -1 : 1;
|
||||
idx = -(idx + 1);
|
||||
if (idx == Versions.SNAPSHOT_INTS.length)
|
||||
return -1;
|
||||
|
||||
return this.compareToRelease(Versions.SNAPSHOT_PREV[idx]) <= 0 ? -1 : 1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
int compareToImpl(@NotNull GameVersionNumber other) {
|
||||
if (other instanceof Release)
|
||||
return compareToRelease((Release) other);
|
||||
if (other instanceof Release release)
|
||||
return compareToRelease(release);
|
||||
|
||||
if (other instanceof Snapshot)
|
||||
return compareToSnapshot((Snapshot) other);
|
||||
if (other instanceof LegacySnapshot snapshot)
|
||||
return compareToSnapshot(snapshot);
|
||||
|
||||
if (other instanceof Special)
|
||||
return -((Special) other).compareToReleaseOrSnapshot(this);
|
||||
if (other instanceof Special special)
|
||||
return -special.compareToReleaseOrSnapshot(this);
|
||||
|
||||
throw new AssertionError(other.getClass());
|
||||
}
|
||||
@@ -412,7 +518,7 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
|
||||
return patch;
|
||||
}
|
||||
|
||||
public int getEaType() {
|
||||
public ReleaseType getEaType() {
|
||||
return eaType;
|
||||
}
|
||||
|
||||
@@ -420,28 +526,65 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
|
||||
return eaVersion;
|
||||
}
|
||||
|
||||
public Additional getAdditional() {
|
||||
return additional;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(major, minor, patch, eaType, eaVersion);
|
||||
return Objects.hash(major, minor, patch, eaType, eaVersion, additional);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
return o instanceof Release other
|
||||
&& major == other.major
|
||||
&& minor == other.minor
|
||||
&& patch == other.patch
|
||||
&& eaType == other.eaType
|
||||
&& eaVersion.equals(other.eaVersion);
|
||||
return o instanceof Release that
|
||||
&& this.major == that.major
|
||||
&& this.minor == that.minor
|
||||
&& this.patch == that.patch
|
||||
&& this.eaType == that.eaType
|
||||
&& this.eaVersion.equals(that.eaVersion)
|
||||
&& this.additional == that.additional;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ToStringBuilder buildDebugString() {
|
||||
return super.buildDebugString()
|
||||
.append("major", major)
|
||||
.append("minor", minor)
|
||||
.append("patch", patch)
|
||||
.append("eaType", eaType)
|
||||
.append("eaVersion", eaVersion)
|
||||
.append("additional", additional);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Snapshot extends GameVersionNumber {
|
||||
static Snapshot parse(String value) {
|
||||
if (value.length() != 6 || value.charAt(2) != 'w')
|
||||
/// Legacy snapshot version numbers like `25w46a`.
|
||||
public static final class LegacySnapshot extends GameVersionNumber {
|
||||
static LegacySnapshot parse(String value) {
|
||||
if (value.length() < 6 || value.charAt(2) != 'w')
|
||||
throw new IllegalArgumentException(value);
|
||||
|
||||
int prefixLength;
|
||||
boolean unobfuscated;
|
||||
String normalized;
|
||||
if (value.endsWith("_unobfuscated")) {
|
||||
prefixLength = value.length() - "_unobfuscated".length();
|
||||
unobfuscated = true;
|
||||
normalized = value;
|
||||
} else if (value.endsWith(" Unobfuscated")) {
|
||||
prefixLength = value.length() - " Unobfuscated".length();
|
||||
unobfuscated = true;
|
||||
normalized = value.substring(0, prefixLength) + "_unobfuscated";
|
||||
} else {
|
||||
prefixLength = value.length();
|
||||
unobfuscated = false;
|
||||
normalized = value;
|
||||
}
|
||||
|
||||
if (prefixLength != 6) {
|
||||
throw new IllegalArgumentException(value);
|
||||
}
|
||||
|
||||
int year;
|
||||
int week;
|
||||
try {
|
||||
@@ -452,21 +595,21 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
|
||||
}
|
||||
|
||||
char suffix = value.charAt(5);
|
||||
if ((suffix < 'a' || suffix > 'z') && suffix != '~')
|
||||
if (suffix < 'a' || suffix > 'z')
|
||||
throw new IllegalArgumentException(value);
|
||||
|
||||
return new Snapshot(value, year, week, suffix);
|
||||
return new LegacySnapshot(value, normalized, year, week, suffix, unobfuscated);
|
||||
}
|
||||
|
||||
static int toInt(int year, int week, char suffix) {
|
||||
return (year << 16) | (week << 8) | suffix;
|
||||
static int toInt(int year, int week, char suffix, boolean unobfuscated) {
|
||||
return (year << 24) | (week << 16) | (suffix << 8) | (unobfuscated ? 1 : 0);
|
||||
}
|
||||
|
||||
final int intValue;
|
||||
|
||||
Snapshot(String value, int year, int week, char suffix) {
|
||||
super(value);
|
||||
this.intValue = toInt(year, week, suffix);
|
||||
LegacySnapshot(String value, String normalized, int year, int week, char suffix, boolean unobfuscated) {
|
||||
super(value, normalized);
|
||||
this.intValue = toInt(year, week, suffix, unobfuscated);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -476,40 +619,52 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
|
||||
|
||||
@Override
|
||||
int compareToImpl(@NotNull GameVersionNumber other) {
|
||||
if (other instanceof Release)
|
||||
return -((Release) other).compareToSnapshot(this);
|
||||
if (other instanceof Release otherRelease)
|
||||
return -otherRelease.compareToSnapshot(this);
|
||||
|
||||
if (other instanceof Snapshot)
|
||||
return Integer.compare(this.intValue, ((Snapshot) other).intValue);
|
||||
if (other instanceof LegacySnapshot otherSnapshot)
|
||||
return Integer.compare(this.intValue, otherSnapshot.intValue);
|
||||
|
||||
if (other instanceof Special)
|
||||
return -((Special) other).compareToReleaseOrSnapshot(this);
|
||||
if (other instanceof Special otherSpecial)
|
||||
return -otherSpecial.compareToReleaseOrSnapshot(this);
|
||||
|
||||
throw new AssertionError(other.getClass());
|
||||
}
|
||||
|
||||
public int getYear() {
|
||||
return (intValue >> 16) & 0xff;
|
||||
return (intValue >> 24) & 0xff;
|
||||
}
|
||||
|
||||
public int getWeek() {
|
||||
return (intValue >> 8) & 0xff;
|
||||
return (intValue >> 16) & 0xff;
|
||||
}
|
||||
|
||||
public char getSuffix() {
|
||||
return (char) (intValue & 0xff);
|
||||
return (char) ((intValue >> 8) & 0xff);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
return o instanceof Snapshot other && this.intValue == other.intValue;
|
||||
public boolean isUnobfuscated() {
|
||||
return (intValue & 0b00000001) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return intValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof LegacySnapshot that && this.intValue == that.intValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ToStringBuilder buildDebugString() {
|
||||
return super.buildDebugString()
|
||||
.append("year", getYear())
|
||||
.append("week", getWeek())
|
||||
.append("suffix", getSuffix())
|
||||
.append("unobfuscated", isUnobfuscated());
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Special extends GameVersionNumber {
|
||||
@@ -517,8 +672,8 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
|
||||
|
||||
private GameVersionNumber prev;
|
||||
|
||||
Special(String value) {
|
||||
super(value);
|
||||
Special(String value, String normalized) {
|
||||
super(value, normalized);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -534,13 +689,13 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
|
||||
if (versionNumber != null)
|
||||
return versionNumber;
|
||||
|
||||
return versionNumber = VersionNumber.asVersion(value);
|
||||
return versionNumber = VersionNumber.asVersion(normalized);
|
||||
}
|
||||
|
||||
GameVersionNumber getPrevNormalVersion() {
|
||||
GameVersionNumber v = prev;
|
||||
while (v instanceof Special) {
|
||||
v = ((Special) v).prev;
|
||||
while (v instanceof Special special) {
|
||||
v = special.prev;
|
||||
}
|
||||
|
||||
if (v == null) throw new AssertionError("version: " + value);
|
||||
@@ -567,7 +722,7 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
|
||||
if (other.isUnknown())
|
||||
return -1;
|
||||
|
||||
if (this.value.equals(other.value))
|
||||
if (this.normalized.equals(other.normalized))
|
||||
return 0;
|
||||
|
||||
int c = this.getPrevNormalVersion().compareTo(other.getPrevNormalVersion());
|
||||
@@ -575,11 +730,11 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
|
||||
return c;
|
||||
|
||||
GameVersionNumber v = prev;
|
||||
while (v instanceof Special) {
|
||||
while (v instanceof Special special) {
|
||||
if (v == other)
|
||||
return 1;
|
||||
|
||||
v = ((Special) v).prev;
|
||||
v = special.prev;
|
||||
}
|
||||
|
||||
return -1;
|
||||
@@ -587,27 +742,23 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
|
||||
|
||||
@Override
|
||||
int compareToImpl(@NotNull GameVersionNumber o) {
|
||||
if (o instanceof Release)
|
||||
if (o instanceof Release || o instanceof LegacySnapshot)
|
||||
return compareToReleaseOrSnapshot(o);
|
||||
|
||||
if (o instanceof Snapshot)
|
||||
return compareToReleaseOrSnapshot(o);
|
||||
|
||||
if (o instanceof Special)
|
||||
return compareToSpecial((Special) o);
|
||||
if (o instanceof Special special)
|
||||
return compareToSpecial(special);
|
||||
|
||||
throw new AssertionError(o.getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return value.hashCode();
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof Special that && this.normalized.equals(that.normalized);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
return o instanceof Special other && this.value.equals(other.value);
|
||||
public int hashCode() {
|
||||
return normalized.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -621,10 +772,11 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
|
||||
static {
|
||||
ArrayDeque<String> defaultGameVersions = new ArrayDeque<>(64);
|
||||
|
||||
List<Snapshot> snapshots = new ArrayList<>(1024);
|
||||
List<LegacySnapshot> snapshots = new ArrayList<>(1024);
|
||||
List<Release> snapshotPrev = new ArrayList<>(1024);
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(GameVersionNumber.class.getResourceAsStream("/assets/game/versions.txt"), StandardCharsets.US_ASCII))) {
|
||||
//noinspection DataFlowIssue
|
||||
try (var reader = new BufferedReader(new InputStreamReader(GameVersionNumber.class.getResourceAsStream("/assets/game/versions.txt"), StandardCharsets.US_ASCII))) {
|
||||
Release currentRelease = null;
|
||||
GameVersionNumber prev = null;
|
||||
|
||||
@@ -637,13 +789,13 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
|
||||
if (currentRelease == null)
|
||||
currentRelease = (Release) version;
|
||||
|
||||
if (version instanceof Snapshot snapshot) {
|
||||
if (version instanceof LegacySnapshot snapshot) {
|
||||
snapshots.add(snapshot);
|
||||
snapshotPrev.add(currentRelease);
|
||||
} else if (version instanceof Release) {
|
||||
currentRelease = (Release) version;
|
||||
} else if (version instanceof Release release) {
|
||||
currentRelease = release;
|
||||
|
||||
if (currentRelease.eaType == Release.TYPE_GA) {
|
||||
if (currentRelease.eaType == Release.ReleaseType.GA) {
|
||||
defaultGameVersions.addFirst(currentRelease.value);
|
||||
}
|
||||
} else if (version instanceof Special special) {
|
||||
@@ -658,6 +810,36 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
//noinspection DataFlowIssue
|
||||
try (var reader = new BufferedReader(new InputStreamReader(GameVersionNumber.class.getResourceAsStream("/assets/game/version-alias.csv"), StandardCharsets.US_ASCII))) {
|
||||
for (String line; (line = reader.readLine()) != null; ) {
|
||||
if (line.isEmpty())
|
||||
continue;
|
||||
|
||||
String[] parts = line.split(",");
|
||||
if (parts.length < 2) {
|
||||
LOG.warning("Invalid line: " + line);
|
||||
continue;
|
||||
}
|
||||
|
||||
String normalized = parts[0];
|
||||
Special normalizedVersion = SPECIALS.get(normalized);
|
||||
if (normalizedVersion == null) {
|
||||
LOG.warning("Unknown special version: " + normalized);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int i = 1; i < parts.length; i++) {
|
||||
String version = parts[i];
|
||||
Special versionNumber = new Special(version, normalized);
|
||||
versionNumber.prev = normalizedVersion.prev;
|
||||
SPECIALS.put(version, versionNumber);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
DEFAULT_GAME_VERSIONS = defaultGameVersions.toArray(new String[0]);
|
||||
|
||||
SNAPSHOT_INTS = new int[snapshots.size()];
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
{
|
||||
"versions": [
|
||||
{
|
||||
"id": "1.21.11-pre2_unobfuscated",
|
||||
"type": "unobfuscated",
|
||||
"url": "https://piston-meta.mojang.com/v1/packages/2955ce0af0512fdfe53ff0740b017344acf6f397/1.21.11-pre2_unobfuscated.json",
|
||||
"time": "2025-11-21T12:07:21+00:00",
|
||||
"releaseTime": "2025-11-21T12:07:21+00:00"
|
||||
},
|
||||
{
|
||||
"id": "1.21.11-pre1_unobfuscated",
|
||||
"type": "unobfuscated",
|
||||
"url": "https://piston-meta.mojang.com/v1/packages/9c267f8dda2728bae55201a753cdd07b584709f1/1.21.11-pre1_unobfuscated.json",
|
||||
"time": "2025-11-19T08:30:46+00:00",
|
||||
"releaseTime": "2025-11-19T08:30:46+00:00"
|
||||
},
|
||||
{
|
||||
"id": "25w46a_unobfuscated",
|
||||
"type": "unobfuscated",
|
||||
@@ -71,63 +85,63 @@
|
||||
"releaseTime": "2021-07-13T12:54:19+00:00"
|
||||
},
|
||||
{
|
||||
"id": "1_16_combat-6",
|
||||
"id": "1.16_combat-6",
|
||||
"type": "pending",
|
||||
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_16_combat-6/1_16_combat-6.json",
|
||||
"time": "2020-08-26T06:24:28+00:00",
|
||||
"releaseTime": "2020-08-26T06:24:28+00:00"
|
||||
},
|
||||
{
|
||||
"id": "1_16_combat-5",
|
||||
"id": "1.16_combat-5",
|
||||
"type": "pending",
|
||||
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_16_combat-5/1_16_combat-5.json",
|
||||
"time": "2020-08-21T09:23:13+00:00",
|
||||
"releaseTime": "2020-08-21T09:23:13+00:00"
|
||||
},
|
||||
{
|
||||
"id": "1_16_combat-4",
|
||||
"id": "1.16_combat-4",
|
||||
"type": "pending",
|
||||
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_16_combat-4/1_16_combat-4.json",
|
||||
"time": "2020-08-19T11:14:58+00:00",
|
||||
"releaseTime": "2020-08-19T11:14:58+00:00"
|
||||
},
|
||||
{
|
||||
"id": "1_16_combat-3",
|
||||
"id": "1.16_combat-3",
|
||||
"type": "pending",
|
||||
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_16_combat-3/1_16_combat-3.json",
|
||||
"time": "2020-08-14T09:02:15+00:00",
|
||||
"releaseTime": "2020-08-14T09:02:15+00:00"
|
||||
},
|
||||
{
|
||||
"id": "1_16_combat-2",
|
||||
"id": "1.16_combat-2",
|
||||
"type": "pending",
|
||||
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_16_combat-2/1_16_combat-2.json",
|
||||
"time": "2020-08-13T11:56:30+00:00",
|
||||
"releaseTime": "2020-08-13T11:56:30+00:00"
|
||||
},
|
||||
{
|
||||
"id": "1_16_combat-1",
|
||||
"id": "1.16_combat-1",
|
||||
"type": "pending",
|
||||
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_16_combat-1/1_16_combat-1.json",
|
||||
"time": "2020-08-12T14:07:25+00:00",
|
||||
"releaseTime": "2020-08-12T14:07:25+00:00"
|
||||
},
|
||||
{
|
||||
"id": "1_16_combat-0",
|
||||
"id": "1.16_combat-0",
|
||||
"type": "pending",
|
||||
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_16_combat-0/1_16_combat-0.json",
|
||||
"time": "2020-08-07T10:44:47+00:00",
|
||||
"releaseTime": "2020-08-07T10:44:47+00:00"
|
||||
},
|
||||
{
|
||||
"id": "1_15_combat-6",
|
||||
"id": "1.15_combat-6",
|
||||
"type": "pending",
|
||||
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_15_combat-6/1_15_combat-6.json",
|
||||
"time": "2020-01-15T09:46:35+00:00",
|
||||
"releaseTime": "2020-01-15T09:46:35+00:00"
|
||||
},
|
||||
{
|
||||
"id": "1_15_combat-1",
|
||||
"id": "1.15_combat-1",
|
||||
"type": "pending",
|
||||
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_15_combat-1/1_15_combat-1.json",
|
||||
"time": "2019-11-29T15:41:39+00:00",
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
1.14_combat-212796,1.14.3 - Combat Test
|
||||
1.14_combat-0,Combat Test 2
|
||||
1.14_combat-3,Combat Test 3
|
||||
1.15_combat-1,Combat Test 4
|
||||
1.15_combat-6,Combat Test 5
|
||||
1.16_combat-0,Combat Test 6
|
||||
1.16_combat-1,Combat Test 7
|
||||
1.16_combat-2,Combat Test 7b
|
||||
1.16_combat-3,Combat Test 7c
|
||||
1.16_combat-4,Combat Test 8
|
||||
1.16_combat-5,Combat Test 8b
|
||||
1.16_combat-6,Combat Test 8c
|
||||
1.18_experimental-snapshot-1,1.18 Experimental Snapshot 1
|
||||
1.18_experimental-snapshot-2,1.18 Experimental Snapshot 2
|
||||
1.18_experimental-snapshot-3,1.18 Experimental Snapshot 3
|
||||
1.18_experimental-snapshot-4,1.18 Experimental Snapshot 4
|
||||
1.18_experimental-snapshot-5,1.18 Experimental Snapshot 5
|
||||
1.18_experimental-snapshot-6,1.18 Experimental Snapshot 6
|
||||
1.19_deep_dark_experimental_snapshot-1,Deep Dark Experimental Snapshot 1
|
||||
20w14infinite,20w14~
|
||||
|
@@ -452,24 +452,25 @@
|
||||
3D Shareware v1.34
|
||||
19w14a
|
||||
19w14b
|
||||
1.14 Pre-Release 1
|
||||
1.14 Pre-Release 2
|
||||
1.14 Pre-Release 3
|
||||
1.14 Pre-Release 4
|
||||
1.14 Pre-Release 5
|
||||
1.14-pre1
|
||||
1.14-pre2
|
||||
1.14-pre3
|
||||
1.14-pre4
|
||||
1.14-pre5
|
||||
1.14
|
||||
1.14.1 Pre-Release 1
|
||||
1.14.1 Pre-Release 2
|
||||
1.14.1-pre1
|
||||
1.14.1-pre2
|
||||
1.14.1
|
||||
1.14.2 Pre-Release 1
|
||||
1.14.2 Pre-Release 2
|
||||
1.14.2 Pre-Release 3
|
||||
1.14.2 Pre-Release 4
|
||||
1.14.2-pre1
|
||||
1.14.2-pre2
|
||||
1.14.2-pre3
|
||||
1.14.2-pre4
|
||||
1.14.2
|
||||
1.14.3-pre1
|
||||
1.14.3-pre2
|
||||
1.14.3-pre3
|
||||
1.14.3-pre4
|
||||
1.14_combat-212796
|
||||
1.14.3
|
||||
1.14.4-pre1
|
||||
1.14.4-pre2
|
||||
@@ -479,6 +480,8 @@
|
||||
1.14.4-pre6
|
||||
1.14.4-pre7
|
||||
1.14.4
|
||||
1.14_combat-0
|
||||
1.14_combat-3
|
||||
19w34a
|
||||
19w35a
|
||||
19w36a
|
||||
@@ -497,6 +500,7 @@
|
||||
1.15-pre1
|
||||
1.15-pre2
|
||||
1.15-pre3
|
||||
1.15_combat-1
|
||||
1.15-pre4
|
||||
1.15-pre5
|
||||
1.15-pre6
|
||||
@@ -506,6 +510,7 @@
|
||||
1.15.1
|
||||
1.15.2-pre1
|
||||
1.15.2-pre2
|
||||
1.15_combat-6
|
||||
1.15.2
|
||||
20w06a
|
||||
20w07a
|
||||
@@ -545,9 +550,16 @@
|
||||
1.16.2-pre1
|
||||
1.16.2-pre2
|
||||
1.16.2-pre3
|
||||
1.16_combat-0
|
||||
1.16.2-rc1
|
||||
1.16.2-rc2
|
||||
1.16.2
|
||||
1.16_combat-1
|
||||
1.16_combat-2
|
||||
1.16_combat-3
|
||||
1.16_combat-4
|
||||
1.16_combat-5
|
||||
1.16_combat-6
|
||||
1.16.3-rc1
|
||||
1.16.3
|
||||
1.16.4-pre1
|
||||
@@ -592,6 +604,13 @@
|
||||
1.17.1-rc1
|
||||
1.17.1-rc2
|
||||
1.17.1
|
||||
1.18_experimental-snapshot-1
|
||||
1.18_experimental-snapshot-2
|
||||
1.18_experimental-snapshot-3
|
||||
1.18_experimental-snapshot-4
|
||||
1.18_experimental-snapshot-5
|
||||
1.18_experimental-snapshot-6
|
||||
1.18_experimental-snapshot-7
|
||||
21w37a
|
||||
21w38a
|
||||
21w39a
|
||||
@@ -622,6 +641,7 @@
|
||||
22w05a
|
||||
22w06a
|
||||
22w07a
|
||||
1.19_deep_dark_experimental_snapshot-1
|
||||
1.18.2-pre1
|
||||
1.18.2-pre2
|
||||
1.18.2-pre3
|
||||
@@ -856,4 +876,8 @@
|
||||
25w45a
|
||||
25w45a_unobfuscated
|
||||
25w46a
|
||||
25w46a_unobfuscated
|
||||
25w46a_unobfuscated
|
||||
1.21.11-pre1
|
||||
1.21.11-pre1_unobfuscated
|
||||
1.21.11-pre2
|
||||
1.21.11-pre2_unobfuscated
|
||||
@@ -1,4 +1,4 @@
|
||||
# https://gitlab.freedesktop.org/mesa/libdrm/-/blob/7d43d9b6fe5df4b234937fce9e51adb1b327ded1/data/amdgpu.ids
|
||||
# https://gitlab.freedesktop.org/mesa/libdrm/-/blob/35a21916c8f621b35a67f3c5994280ae59a4981a/data/amdgpu.ids
|
||||
#
|
||||
# List of AMDGPU IDs
|
||||
#
|
||||
@@ -559,6 +559,7 @@
|
||||
7448, 00, AMD Radeon Pro W7900
|
||||
7449, 00, AMD Radeon Pro W7800 48GB
|
||||
744A, 00, AMD Radeon Pro W7900 Dual Slot
|
||||
744B, 00, AMD Radeon Pro W7900D
|
||||
744C, C8, AMD Radeon RX 7900 XTX
|
||||
744C, CC, AMD Radeon RX 7900 XT
|
||||
744C, CE, AMD Radeon RX 7900 GRE
|
||||
@@ -569,6 +570,7 @@
|
||||
7470, 00, AMD Radeon Pro W7700
|
||||
747E, C8, AMD Radeon RX 7800 XT
|
||||
747E, D8, AMD Radeon RX 7800M
|
||||
747E, DB, AMD Radeon RX 7700
|
||||
747E, FF, AMD Radeon RX 7700 XT
|
||||
7480, 00, AMD Radeon Pro W7600
|
||||
7480, C0, AMD Radeon RX 7600 XT
|
||||
@@ -586,6 +588,7 @@
|
||||
74A1, 00, AMD Instinct MI300X
|
||||
74A2, 00, AMD Instinct MI308X
|
||||
74A5, 00, AMD Instinct MI325X
|
||||
74A8, 00, AMD Instinct MI308X HF
|
||||
74A9, 00, AMD Instinct MI300X HF
|
||||
74B5, 00, AMD Instinct MI300X VF
|
||||
74B6, 00, AMD Instinct MI308X
|
||||
@@ -594,6 +597,12 @@
|
||||
7550, C2, AMD Radeon RX 9070 GRE
|
||||
7550, C3, AMD Radeon RX 9070
|
||||
7551, C0, AMD Radeon AI PRO R9700
|
||||
7590, C0, AMD Radeon RX 9060 XT
|
||||
7590, C7, AMD Radeon RX 9060
|
||||
75A0, C0, AMD Instinct MI350X
|
||||
75A3, C0, AMD Instinct MI355X
|
||||
75B0, C0, AMD Instinct MI350X VF
|
||||
75B3, C0, AMD Instinct MI355X VF
|
||||
9830, 00, AMD Radeon HD 8400 / R3 Series
|
||||
9831, 00, AMD Radeon HD 8400E
|
||||
9832, 00, AMD Radeon HD 8330
|
||||
@@ -689,4 +698,4 @@
|
||||
98E4, E9, AMD Radeon R4 Graphics
|
||||
98E4, EA, AMD Radeon R4 Graphics
|
||||
98E4, EB, AMD Radeon R3 Graphics
|
||||
98E4, EB, AMD Radeon R4 Graphics
|
||||
98E4, EB, AMD Radeon R4 Graphics
|
||||
+219
-53
@@ -25,6 +25,7 @@ import java.io.InputStreamReader;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.jackhuang.hmcl.util.versioning.GameVersionNumber.asGameVersion;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
@@ -34,6 +35,8 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
*/
|
||||
public final class GameVersionNumberTest {
|
||||
|
||||
//region Helpers
|
||||
|
||||
private static List<String> readVersions() {
|
||||
List<String> versions = new ArrayList<>();
|
||||
|
||||
@@ -48,18 +51,8 @@ public final class GameVersionNumberTest {
|
||||
return versions;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSortVersions() {
|
||||
List<String> versions = readVersions();
|
||||
List<String> copied = new ArrayList<>(versions);
|
||||
Collections.shuffle(copied, new Random(0));
|
||||
copied.sort(Comparator.comparing(GameVersionNumber::asGameVersion));
|
||||
|
||||
assertIterableEquals(versions, copied);
|
||||
}
|
||||
|
||||
private static String errorMessage(String version1, String version2) {
|
||||
return String.format("version1=%s, version2=%s", version1, version2);
|
||||
private static Supplier<String> errorMessage(GameVersionNumber version1, GameVersionNumber version2) {
|
||||
return () -> "version1=%s, version2=%s".formatted(version1.toDebugString(), version2.toDebugString());
|
||||
}
|
||||
|
||||
private static void assertGameVersionEquals(String version) {
|
||||
@@ -67,25 +60,42 @@ public final class GameVersionNumberTest {
|
||||
}
|
||||
|
||||
private static void assertGameVersionEquals(String version1, String version2) {
|
||||
assertEquals(0, asGameVersion(version1).compareTo(version2), errorMessage(version1, version2));
|
||||
assertEquals(asGameVersion(version1), asGameVersion(version2), errorMessage(version1, version2));
|
||||
}
|
||||
|
||||
private static String toString(GameVersionNumber gameVersionNumber) {
|
||||
return gameVersionNumber.getClass().getSimpleName();
|
||||
GameVersionNumber gameVersion1 = asGameVersion(version1);
|
||||
GameVersionNumber gameVersion2 = asGameVersion(version2);
|
||||
assertEquals(0, gameVersion1.compareTo(gameVersion2), errorMessage(gameVersion1, gameVersion2));
|
||||
assertEquals(0, gameVersion2.compareTo(gameVersion1), errorMessage(gameVersion1, gameVersion2));
|
||||
assertEquals(gameVersion1, gameVersion2, errorMessage(gameVersion1, gameVersion2));
|
||||
assertEquals(gameVersion2, gameVersion1, errorMessage(gameVersion1, gameVersion2));
|
||||
assertEquals(gameVersion1.hashCode(), gameVersion2.hashCode(), errorMessage(gameVersion1, gameVersion2));
|
||||
}
|
||||
|
||||
private static void assertOrder(String... versions) {
|
||||
var gameVersionNumbers = new GameVersionNumber[versions.length];
|
||||
for (int i = 0; i < versions.length; i++) {
|
||||
gameVersionNumbers[i] = asGameVersion(versions[i]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < versions.length - 1; i++) {
|
||||
GameVersionNumber version1 = asGameVersion(versions[i]);
|
||||
GameVersionNumber version1 = gameVersionNumbers[i];
|
||||
|
||||
for (int j = 0; j < i; j++) {
|
||||
GameVersionNumber version2 = gameVersionNumbers[j];
|
||||
|
||||
assertTrue(version1.compareTo(version2) > 0, errorMessage(version1, version2));
|
||||
assertTrue(version2.compareTo(version1) < 0, errorMessage(version1, version2));
|
||||
assertNotEquals(version1, version2, errorMessage(version1, version2));
|
||||
assertNotEquals(version2, version1, errorMessage(version1, version2));
|
||||
}
|
||||
|
||||
assertGameVersionEquals(versions[i]);
|
||||
|
||||
for (int j = i + 1; j < versions.length; j++) {
|
||||
GameVersionNumber version2 = asGameVersion(versions[j]);
|
||||
GameVersionNumber version2 = gameVersionNumbers[j];
|
||||
|
||||
assertEquals(-1, version1.compareTo(version2), String.format("version1=%s (%s), version2=%s (%s)", versions[i], toString(version1), versions[j], toString(version2)));
|
||||
assertEquals(1, version2.compareTo(version1), String.format("version1=%s (%s), version2=%s (%s)", versions[i], toString(version1), versions[j], toString(version2)));
|
||||
assertTrue(version1.compareTo(version2) < 0, errorMessage(version1, version2));
|
||||
assertTrue(version2.compareTo(version1) > 0, errorMessage(version1, version2));
|
||||
assertNotEquals(version1, version2, errorMessage(version1, version2));
|
||||
assertNotEquals(version2, version1, errorMessage(version1, version2));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +110,8 @@ public final class GameVersionNumberTest {
|
||||
assertEquals(VersionNumber.asVersion(versionNumber), old.versionNumber);
|
||||
}
|
||||
|
||||
//endregion Helpers
|
||||
|
||||
private static boolean isAprilFools(String version) {
|
||||
return asGameVersion(version).isAprilFools();
|
||||
}
|
||||
@@ -124,6 +136,31 @@ public final class GameVersionNumberTest {
|
||||
assertFalse(isAprilFools("25w45a_unobfuscated"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSortVersions() {
|
||||
List<String> versions = readVersions();
|
||||
|
||||
{
|
||||
List<String> copied = new ArrayList<>(versions);
|
||||
copied.sort(Comparator.comparing(GameVersionNumber::asGameVersion));
|
||||
assertIterableEquals(versions, copied);
|
||||
}
|
||||
|
||||
{
|
||||
List<String> copied = new ArrayList<>(versions);
|
||||
Collections.reverse(copied);
|
||||
copied.sort(Comparator.comparing(GameVersionNumber::asGameVersion));
|
||||
assertIterableEquals(versions, copied);
|
||||
}
|
||||
|
||||
for (int randomSeed = 0; randomSeed < 5; randomSeed++) {
|
||||
List<String> copied = new ArrayList<>(versions);
|
||||
Collections.shuffle(copied, new Random(randomSeed));
|
||||
copied.sort(Comparator.comparing(GameVersionNumber::asGameVersion));
|
||||
assertIterableEquals(versions, copied);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseOld() {
|
||||
assertOldVersion("rd-132211", GameVersionNumber.Type.PRE_CLASSIC, "132211");
|
||||
@@ -136,36 +173,96 @@ public final class GameVersionNumberTest {
|
||||
assertOldVersion("a1.0.13_01-1", GameVersionNumber.Type.ALPHA, "1.0.13_01-1");
|
||||
assertOldVersion("b1.0", GameVersionNumber.Type.BETA, "1.0");
|
||||
assertOldVersion("b1.0_01", GameVersionNumber.Type.BETA, "1.0_01");
|
||||
assertOldVersion("b1.6-tb3", GameVersionNumber.Type.BETA, "1.6-tb3");
|
||||
assertOldVersion("b1.8-pre1-2", GameVersionNumber.Type.BETA, "1.8-pre1-2");
|
||||
assertOldVersion("b1.9-pre1", GameVersionNumber.Type.BETA, "1.9-pre1");
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse(""));
|
||||
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse("1.21"));
|
||||
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse("r-132211"));
|
||||
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse("rd-"));
|
||||
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse("rd-a"));
|
||||
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse("i-20100223"));
|
||||
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse("in-"));
|
||||
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse("in-a"));
|
||||
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse("inf-"));
|
||||
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse("inf-a"));
|
||||
}
|
||||
|
||||
private static void testParseLegacySnapshot(int year, int week, char suffix) {
|
||||
String raw = "%02dw%02d%s".formatted(year, week, suffix);
|
||||
var rawVersion = (GameVersionNumber.LegacySnapshot) asGameVersion(raw);
|
||||
assertInstanceOf(GameVersionNumber.LegacySnapshot.class, rawVersion);
|
||||
assertEquals(raw, rawVersion.toString());
|
||||
assertEquals(raw, rawVersion.toNormalizedString());
|
||||
assertEquals(year, rawVersion.getYear());
|
||||
assertEquals(week, rawVersion.getWeek());
|
||||
assertEquals(suffix, rawVersion.getSuffix());
|
||||
assertFalse(rawVersion.isUnobfuscated());
|
||||
|
||||
var unobfuscated = raw + "_unobfuscated";
|
||||
var unobfuscatedVersion = (GameVersionNumber.LegacySnapshot) asGameVersion(unobfuscated);
|
||||
assertInstanceOf(GameVersionNumber.LegacySnapshot.class, rawVersion);
|
||||
assertEquals(unobfuscated, unobfuscatedVersion.toString());
|
||||
assertEquals(unobfuscated, unobfuscatedVersion.toNormalizedString());
|
||||
assertEquals(year, unobfuscatedVersion.getYear());
|
||||
assertEquals(week, unobfuscatedVersion.getWeek());
|
||||
assertEquals(suffix, unobfuscatedVersion.getSuffix());
|
||||
assertTrue(unobfuscatedVersion.isUnobfuscated());
|
||||
|
||||
var unobfuscated2 = raw + " Unobfuscated";
|
||||
var unobfuscatedVersion2 = (GameVersionNumber.LegacySnapshot) asGameVersion(unobfuscated2);
|
||||
assertInstanceOf(GameVersionNumber.LegacySnapshot.class, rawVersion);
|
||||
assertEquals(unobfuscated2, unobfuscatedVersion2.toString());
|
||||
assertEquals(unobfuscated, unobfuscatedVersion2.toNormalizedString());
|
||||
assertEquals(year, unobfuscatedVersion2.getYear());
|
||||
assertEquals(week, unobfuscatedVersion2.getWeek());
|
||||
assertEquals(suffix, unobfuscatedVersion2.getSuffix());
|
||||
assertTrue(unobfuscatedVersion2.isUnobfuscated());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseNew() {
|
||||
List<String> versions = readVersions();
|
||||
for (String version : versions) {
|
||||
assertFalse(asGameVersion(version) instanceof GameVersionNumber.Old, "version=" + version);
|
||||
GameVersionNumber gameVersion = asGameVersion(version);
|
||||
assertFalse(gameVersion instanceof GameVersionNumber.Old, "version=" + gameVersion.toDebugString());
|
||||
}
|
||||
|
||||
testParseLegacySnapshot(25, 46, 'a');
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parse("2.1"));
|
||||
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.LegacySnapshot.parse("1.0"));
|
||||
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.LegacySnapshot.parse("1.100.1"));
|
||||
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.LegacySnapshot.parse("aawbba"));
|
||||
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.LegacySnapshot.parse("13w12A"));
|
||||
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.LegacySnapshot.parse("13w12~"));
|
||||
}
|
||||
|
||||
private static void assertSimpleReleaseVersion(String simpleReleaseVersion, int minor, int patch) {
|
||||
private static void assertSimpleReleaseVersion(String simpleReleaseVersion, int major, int minor, int patch) {
|
||||
GameVersionNumber.Release release = GameVersionNumber.Release.parseSimple(simpleReleaseVersion);
|
||||
assertAll("Assert Simple Release Version " + simpleReleaseVersion,
|
||||
() -> assertEquals(1, release.getMajor()),
|
||||
() -> assertEquals(major, release.getMajor()),
|
||||
() -> assertEquals(minor, release.getMinor()),
|
||||
() -> assertEquals(patch, release.getPatch()),
|
||||
() -> assertEquals(GameVersionNumber.Release.TYPE_UNKNOWN, release.getEaType()),
|
||||
() -> assertEquals(GameVersionNumber.Release.ReleaseType.UNKNOWN, release.getEaType()),
|
||||
() -> assertEquals(VersionNumber.ZERO, release.getEaVersion())
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseSimpleRelease() {
|
||||
assertSimpleReleaseVersion("1.0", 0, 0);
|
||||
assertSimpleReleaseVersion("1.13", 13, 0);
|
||||
assertSimpleReleaseVersion("1.21.8", 21, 8);
|
||||
assertSimpleReleaseVersion("1.0", 1, 0, 0);
|
||||
assertSimpleReleaseVersion("1.13", 1, 13, 0);
|
||||
assertSimpleReleaseVersion("1.21.8", 1, 21, 8);
|
||||
assertSimpleReleaseVersion("26.1", 26, 1, 0);
|
||||
assertSimpleReleaseVersion("26.1.1", 26, 1, 1);
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple("26"));
|
||||
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple("24.0.0"));
|
||||
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple("24.0"));
|
||||
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple("2.0"));
|
||||
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple("1"));
|
||||
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple("1..0"));
|
||||
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple("1.0."));
|
||||
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple("1.a"));
|
||||
@@ -186,13 +283,13 @@ public final class GameVersionNumberTest {
|
||||
"0.0",
|
||||
"1.0",
|
||||
"1.99",
|
||||
"1.99.1-unknown1",
|
||||
"1.99.1-pre1",
|
||||
"1.99.1 Pre-Release 2",
|
||||
"1.99.1-rc1",
|
||||
"1.99.1",
|
||||
"1.100",
|
||||
"1.100.1"
|
||||
"1.100.1",
|
||||
"26.1"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -202,7 +299,6 @@ public final class GameVersionNumberTest {
|
||||
"90w01a",
|
||||
"90w01b",
|
||||
"90w01e",
|
||||
"90w01~",
|
||||
"90w02a"
|
||||
);
|
||||
}
|
||||
@@ -257,6 +353,7 @@ public final class GameVersionNumberTest {
|
||||
"1.14",
|
||||
"1.15.2",
|
||||
"20w06a",
|
||||
"20w13b",
|
||||
"20w14infinite",
|
||||
"20w22a",
|
||||
"1.16-pre1",
|
||||
@@ -273,10 +370,21 @@ public final class GameVersionNumberTest {
|
||||
"24w13a",
|
||||
"24w14potato",
|
||||
"24w14a",
|
||||
"25w45a",
|
||||
"25w45a_unobfuscated",
|
||||
"Unknown",
|
||||
"100.0"
|
||||
"25w46a",
|
||||
"25w46a_unobfuscated",
|
||||
"1.21.11-pre1",
|
||||
"1.21.11-pre1_unobfuscated",
|
||||
"1.21.11-pre2",
|
||||
"1.21.11-pre2_unobfuscated",
|
||||
"99w99a",
|
||||
"26.1-snapshot-1",
|
||||
"26.1-snapshot-2",
|
||||
"26.1",
|
||||
"26.2-snapshot-1",
|
||||
"26.2-snapshot-2",
|
||||
"26.2",
|
||||
"100.0",
|
||||
"Unknown"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -312,26 +420,84 @@ public final class GameVersionNumberTest {
|
||||
);
|
||||
}
|
||||
|
||||
private static void assertNormalized(String normalized, String version) {
|
||||
assertGameVersionEquals(version);
|
||||
assertGameVersionEquals(normalized, version);
|
||||
assertEquals(normalized, asGameVersion(version).toNormalizedString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToNormalizedString() {
|
||||
for (String version : readVersions()) {
|
||||
assertNormalized(version, version);
|
||||
}
|
||||
|
||||
assertNormalized("26.1-snapshot-1", "26.1 Snapshot 1");
|
||||
assertNormalized("1.21.11-pre3", "1.21.11 Pre-Release 3");
|
||||
assertNormalized("1.21.11-pre3_unobfuscated", "1.21.11 Pre-Release 3 Unobfuscated");
|
||||
assertNormalized("1.21.11-pre3_unobfuscated", "1.21.11-pre3 Unobfuscated");
|
||||
assertNormalized("1.21.11-rc1", "1.21.11 Release Candidate 1");
|
||||
assertNormalized("1.21.11-rc1_unobfuscated", "1.21.11 Release Candidate 1 Unobfuscated");
|
||||
assertNormalized("1.14_combat-212796", "1.14.3 - Combat Test");
|
||||
assertNormalized("1.14_combat-0", "Combat Test 2");
|
||||
assertNormalized("1.14_combat-3", "Combat Test 3");
|
||||
assertNormalized("1.15_combat-1", "Combat Test 4");
|
||||
assertNormalized("1.15_combat-6", "Combat Test 5");
|
||||
assertNormalized("1.16_combat-0", "Combat Test 6");
|
||||
assertNormalized("1.16_combat-1", "Combat Test 7");
|
||||
assertNormalized("1.16_combat-2", "Combat Test 7b");
|
||||
assertNormalized("1.16_combat-3", "Combat Test 7c");
|
||||
assertNormalized("1.16_combat-4", "Combat Test 8");
|
||||
assertNormalized("1.16_combat-5", "Combat Test 8b");
|
||||
assertNormalized("1.16_combat-6", "Combat Test 8c");
|
||||
assertNormalized("1.18_experimental-snapshot-1", "1.18 Experimental Snapshot 1");
|
||||
assertNormalized("1.18_experimental-snapshot-2", "1.18 Experimental Snapshot 2");
|
||||
assertNormalized("1.18_experimental-snapshot-3", "1.18 Experimental Snapshot 3");
|
||||
assertNormalized("1.18_experimental-snapshot-4", "1.18 Experimental Snapshot 4");
|
||||
assertNormalized("1.18_experimental-snapshot-5", "1.18 Experimental Snapshot 5");
|
||||
assertNormalized("1.18_experimental-snapshot-6", "1.18 Experimental Snapshot 6");
|
||||
assertNormalized("1.19_deep_dark_experimental_snapshot-1", "Deep Dark Experimental Snapshot 1");
|
||||
assertNormalized("20w14infinite", "20w14~");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAtLeast() {
|
||||
assertTrue(asGameVersion("1.13").isAtLeast("1.13", "17w43a"));
|
||||
assertTrue(asGameVersion("1.13.1").isAtLeast("1.13", "17w43a"));
|
||||
assertTrue(asGameVersion("1.14").isAtLeast("1.13", "17w43a"));
|
||||
assertTrue(asGameVersion("1.13-rc1").isAtLeast("1.13", "17w43a"));
|
||||
assertTrue(asGameVersion("1.13-pre1").isAtLeast("1.13", "17w43a"));
|
||||
assertTrue(asGameVersion("17w43a").isAtLeast("1.13", "17w43a"));
|
||||
assertTrue(asGameVersion("17w43b").isAtLeast("1.13", "17w43a"));
|
||||
assertTrue(asGameVersion("17w45a").isAtLeast("1.13", "17w43a"));
|
||||
assertTrue(asGameVersion("1.13").isAtLeast("1.13", "17w43a", true));
|
||||
assertTrue(asGameVersion("1.13").isAtLeast("1.13", "17w43a", false));
|
||||
assertTrue(asGameVersion("1.13.1").isAtLeast("1.13", "17w43a", true));
|
||||
assertTrue(asGameVersion("1.13.1").isAtLeast("1.13", "17w43a", false));
|
||||
assertTrue(asGameVersion("1.14").isAtLeast("1.13", "17w43a", true));
|
||||
assertTrue(asGameVersion("1.14").isAtLeast("1.13", "17w43a", false));
|
||||
assertTrue(asGameVersion("1.13-rc1").isAtLeast("1.13", "17w43a", false));
|
||||
assertTrue(asGameVersion("1.13-pre1").isAtLeast("1.13", "17w43a", false));
|
||||
assertTrue(asGameVersion("17w43a").isAtLeast("1.13", "17w43a", true));
|
||||
assertTrue(asGameVersion("17w43a").isAtLeast("1.13", "17w43a", false));
|
||||
assertTrue(asGameVersion("17w43b").isAtLeast("1.13", "17w43a", true));
|
||||
assertTrue(asGameVersion("17w43b").isAtLeast("1.13", "17w43a", false));
|
||||
assertTrue(asGameVersion("17w45a").isAtLeast("1.13", "17w43a", true));
|
||||
assertTrue(asGameVersion("17w45a").isAtLeast("1.13", "17w43a", false));
|
||||
|
||||
assertFalse(asGameVersion("17w31a").isAtLeast("1.13", "17w43a"));
|
||||
assertFalse(asGameVersion("1.12").isAtLeast("1.13", "17w43a"));
|
||||
assertFalse(asGameVersion("1.12.2").isAtLeast("1.13", "17w43a"));
|
||||
assertFalse(asGameVersion("1.12.2-pre1").isAtLeast("1.13", "17w43a"));
|
||||
assertFalse(asGameVersion("rd-132211").isAtLeast("1.13", "17w43a"));
|
||||
assertFalse(asGameVersion("a1.0.6").isAtLeast("1.13", "17w43a"));
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> asGameVersion("1.13").isAtLeast("17w43a", "17w43a"));
|
||||
assertThrows(IllegalArgumentException.class, () -> asGameVersion("17w43a").isAtLeast("1.13", "1.13"));
|
||||
assertThrows(IllegalArgumentException.class, () -> asGameVersion("17w43a").isAtLeast("1.13", "22w13oneblockatatime"));
|
||||
assertFalse(asGameVersion("1.13-rc1").isAtLeast("1.13", "17w43a", true));
|
||||
assertFalse(asGameVersion("1.13-pre1").isAtLeast("1.13", "17w43a", true));
|
||||
assertFalse(asGameVersion("17w31a").isAtLeast("1.13", "17w43a", true));
|
||||
assertFalse(asGameVersion("17w31a").isAtLeast("1.13", "17w43a", false));
|
||||
assertFalse(asGameVersion("1.12").isAtLeast("1.13", "17w43a", true));
|
||||
assertFalse(asGameVersion("1.12").isAtLeast("1.13", "17w43a", false));
|
||||
assertFalse(asGameVersion("1.12.2").isAtLeast("1.13", "17w43a", true));
|
||||
assertFalse(asGameVersion("1.12.2").isAtLeast("1.13", "17w43a", false));
|
||||
assertFalse(asGameVersion("1.12.2-pre1").isAtLeast("1.13", "17w43a", true));
|
||||
assertFalse(asGameVersion("1.12.2-pre1").isAtLeast("1.13", "17w43a", false));
|
||||
assertFalse(asGameVersion("rd-132211").isAtLeast("1.13", "17w43a", true));
|
||||
assertFalse(asGameVersion("rd-132211").isAtLeast("1.13", "17w43a", false));
|
||||
assertFalse(asGameVersion("a1.0.6").isAtLeast("1.13", "17w43a", true));
|
||||
assertFalse(asGameVersion("a1.0.6").isAtLeast("1.13", "17w43a", false));
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> asGameVersion("1.13").isAtLeast("17w43a", "17w43a", true));
|
||||
assertThrows(IllegalArgumentException.class, () -> asGameVersion("1.13").isAtLeast("17w43a", "17w43a", false));
|
||||
assertThrows(IllegalArgumentException.class, () -> asGameVersion("17w43a").isAtLeast("1.13", "1.13", true));
|
||||
assertThrows(IllegalArgumentException.class, () -> asGameVersion("17w43a").isAtLeast("1.13", "1.13", false));
|
||||
assertThrows(IllegalArgumentException.class, () -> asGameVersion("17w43a").isAtLeast("1.13", "22w13oneblockatatime", true));
|
||||
assertThrows(IllegalArgumentException.class, () -> asGameVersion("17w43a").isAtLeast("1.13", "22w13oneblockatatime", false));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user