Compare commits
78
Commits
v3.12.0.328
...
v3.12.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d095eab8fc | ||
|
|
447edff1ed | ||
|
|
f7024a4c0f | ||
|
|
31c7e3a60f | ||
|
|
43cfe29eeb | ||
|
|
4921804283 | ||
|
|
ae4c152a8b | ||
|
|
e436163be7 | ||
|
|
9b8e65449b | ||
|
|
fbb52f4e8a | ||
|
|
704eb32048 | ||
|
|
730d9b4ce8 | ||
|
|
c7b316ecba | ||
|
|
6df5a02c3f | ||
|
|
1b9fde3d60 | ||
|
|
f435cda6a3 | ||
|
|
4a69fde6e6 | ||
|
|
294a0cd7da | ||
|
|
e057e20b3c | ||
|
|
8c0ca7c731 | ||
|
|
0d591a5429 | ||
|
|
900d7595d5 | ||
|
|
5e18c0aad4 | ||
|
|
f51f982f80 | ||
|
|
7c5430424f | ||
|
|
5909c73c48 | ||
|
|
3166c1d405 | ||
|
|
27c73b4434 | ||
|
|
705f978c43 | ||
|
|
67432bf614 | ||
|
|
378c06656d | ||
|
|
e2d6904269 | ||
|
|
8ef2f8be1b | ||
|
|
584f0f39bc | ||
|
|
6529a3bb03 | ||
|
|
0e6f43ccba | ||
|
|
94b10a8b29 | ||
|
|
dc60af6d96 | ||
|
|
272c5d1b57 | ||
|
|
5bfe945fd0 | ||
|
|
3e8a295cf2 | ||
|
|
0d717b84c4 | ||
|
|
13672a57ff | ||
|
|
b19151a811 | ||
|
|
c7d9bc696a | ||
|
|
f426ea48fa | ||
|
|
8b9eeb68f7 | ||
|
|
413069d334 | ||
|
|
bab81da466 | ||
|
|
1138d063d2 | ||
|
|
041eedb765 | ||
|
|
48046cd416 | ||
|
|
9372c0903c | ||
|
|
ee79c50dea | ||
|
|
ef3c1b8926 | ||
|
|
ade057b819 | ||
|
|
2b1de479e5 | ||
|
|
5502a4efad | ||
|
|
5c36c00479 | ||
|
|
bb92b5ff8a | ||
|
|
8b90464d30 | ||
|
|
aaea473325 | ||
|
|
e8f9f093f4 | ||
|
|
9dc493dd02 | ||
|
|
5cf142df0e | ||
|
|
2a6cd17a3c | ||
|
|
285e361a5f | ||
|
|
0b70dc6d22 | ||
|
|
c8e2802373 | ||
|
|
9ac32a308a | ||
|
|
85cc11be1e | ||
|
|
568fa354ee | ||
|
|
2728be1014 | ||
|
|
388473850d | ||
|
|
1a5b19e7fe | ||
|
|
8d3fa105f7 | ||
|
|
ed43c07d88 | ||
|
|
586dee4f1b |
No files matched your search
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: QQ 群
|
||||
url: https://docs.hmcl.net/groups.html
|
||||
about: Hello Minecraft! Launcher 的官方 QQ 交流群。
|
||||
@@ -14,9 +14,9 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '17'
|
||||
|
||||
@@ -12,9 +12,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '17'
|
||||
@@ -27,28 +27,24 @@ jobs:
|
||||
run: ./gradlew build --no-daemon --parallel
|
||||
env:
|
||||
MICROSOFT_AUTH_ID: ${{ secrets.MICROSOFT_AUTH_ID }}
|
||||
MICROSOFT_AUTH_SECRET: ${{ secrets.MICROSOFT_AUTH_SECRET }}
|
||||
CURSEFORGE_API_KEY: ${{ secrets.CURSEFORGE_API_KEY }}
|
||||
- name: Get short SHA
|
||||
run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV
|
||||
- name: Upload JAR
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: HMCL-${{ env.SHORT_SHA }}-jar
|
||||
path: |
|
||||
HMCL/build/libs/HMCL-*.jar
|
||||
HMCL/build/libs/HMCL-*.jar.sha256
|
||||
path: HMCL/build/libs/HMCL-*.jar
|
||||
archive: false
|
||||
- name: Upload EXE
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: HMCL-${{ env.SHORT_SHA }}-exe
|
||||
path: |
|
||||
HMCL/build/libs/HMCL-*.exe
|
||||
HMCL/build/libs/HMCL-*.exe.sha256
|
||||
path: HMCL/build/libs/HMCL-*.exe
|
||||
archive: false
|
||||
- name: Upload SH
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: HMCL-${{ env.SHORT_SHA }}-sh
|
||||
path: |
|
||||
HMCL/build/libs/HMCL-*.sh
|
||||
HMCL/build/libs/HMCL-*.sh.sha256
|
||||
path: HMCL/build/libs/HMCL-*.sh
|
||||
archive: false
|
||||
@@ -66,6 +66,7 @@ dependencies {
|
||||
}
|
||||
|
||||
embedResources(libs.authlib.injector)
|
||||
embedResources(libs.lwjgl.unsafe.agent)
|
||||
}
|
||||
|
||||
fun digest(algorithm: String, bytes: ByteArray): ByteArray = MessageDigest.getInstance(algorithm).digest(bytes)
|
||||
@@ -155,6 +156,7 @@ val hmclProperties = buildList {
|
||||
add("hmcl.microsoft.auth.id" to microsoftAuthId)
|
||||
add("hmcl.curseforge.apikey" to curseForgeApiKey)
|
||||
add("hmcl.authlib-injector.version" to libs.authlib.injector.get().version!!)
|
||||
add("hmcl.lwjgl-unsafe-agent.version" to libs.lwjgl.unsafe.agent.get().version!!)
|
||||
}
|
||||
|
||||
val hmclPropertiesFile = layout.buildDirectory.file("hmcl.properties")
|
||||
|
||||
@@ -20,8 +20,6 @@
|
||||
package com.jfoenix.controls;
|
||||
|
||||
import com.jfoenix.skins.JFXProgressBarSkin;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.scene.control.ProgressBar;
|
||||
import javafx.scene.control.Skin;
|
||||
|
||||
@@ -51,26 +49,18 @@ public class JFXProgressBar extends ProgressBar {
|
||||
return new JFXProgressBarSkin(this);
|
||||
}
|
||||
|
||||
private boolean smoothProgress = true;
|
||||
|
||||
public boolean isSmoothProgress() {
|
||||
return smoothProgress;
|
||||
}
|
||||
|
||||
public void setSmoothProgress(boolean smoothProgress) {
|
||||
this.smoothProgress = smoothProgress;
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
setPrefWidth(200);
|
||||
getStyleClass().add(DEFAULT_STYLE_CLASS);
|
||||
}
|
||||
|
||||
|
||||
private DoubleProperty secondaryProgress;
|
||||
|
||||
public DoubleProperty secondaryProgressProperty() {
|
||||
if (secondaryProgress == null) {
|
||||
secondaryProgress = new SimpleDoubleProperty(this, "secondaryProgress", INDETERMINATE_PROGRESS);
|
||||
}
|
||||
return secondaryProgress;
|
||||
}
|
||||
|
||||
public double getSecondaryProgress() {
|
||||
return secondaryProgress == null ? INDETERMINATE_PROGRESS : secondaryProgress.get();
|
||||
}
|
||||
|
||||
public void setSecondaryProgress(double secondaryProgress) {
|
||||
secondaryProgressProperty().set(secondaryProgress);
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ import javafx.scene.control.Skin;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
/// JFXSpinner is the material design implementation of a loading spinner.
|
||||
///
|
||||
@@ -71,10 +72,10 @@ public class JFXSpinner extends ProgressIndicator {
|
||||
/// this control.
|
||||
private static final String DEFAULT_STYLE_CLASS = "jfx-spinner";
|
||||
|
||||
private static final double DEFAULT_RADIUS = 12.0;
|
||||
private static final double DEFAULT_RADIUS = 16.0;
|
||||
|
||||
/**
|
||||
* specifies the radius of the spinner node, by default it's set to `12.0`
|
||||
* specifies the radius of the spinner node, by default it's set to `16.0`
|
||||
*/
|
||||
private StyleableDoubleProperty radius;
|
||||
|
||||
@@ -96,25 +97,8 @@ public class JFXSpinner extends ProgressIndicator {
|
||||
this.radiusProperty().set(radius);
|
||||
}
|
||||
|
||||
/// specifies from which angle the spinner should start spinning
|
||||
private StyleableDoubleProperty startingAngle;
|
||||
|
||||
public final StyleableDoubleProperty startingAngleProperty() {
|
||||
if (this.startingAngle == null) {
|
||||
startingAngle = new SimpleStyleableDoubleProperty(StyleableProperties.STARTING_ANGLE,
|
||||
JFXSpinner.this,
|
||||
"startingAngle",
|
||||
0.0);
|
||||
}
|
||||
return this.startingAngle;
|
||||
}
|
||||
|
||||
public final double getStartingAngle() {
|
||||
return startingAngle != null ? startingAngle.get() : 0.0;
|
||||
}
|
||||
|
||||
public final void setStartingAngle(final double startingAngle) {
|
||||
this.startingAngleProperty().set(startingAngle);
|
||||
public double getStartingAngle() {
|
||||
return 360 - ThreadLocalRandom.current().nextDouble() * 720;
|
||||
}
|
||||
|
||||
private static final class StyleableProperties {
|
||||
@@ -128,21 +112,7 @@ public class JFXSpinner extends ProgressIndicator {
|
||||
|
||||
@Override
|
||||
public StyleableDoubleProperty getStyleableProperty(JFXSpinner control) {
|
||||
return control.radius;
|
||||
}
|
||||
};
|
||||
|
||||
private static final CssMetaData<JFXSpinner, Number> STARTING_ANGLE =
|
||||
new CssMetaData<>("-jfx-starting-angle",
|
||||
SizeConverter.getInstance(), 0.0) {
|
||||
@Override
|
||||
public boolean isSettable(JFXSpinner control) {
|
||||
return control.startingAngle == null || !control.startingAngle.isBound();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StyleableDoubleProperty getStyleableProperty(JFXSpinner control) {
|
||||
return control.startingAngle;
|
||||
return control.radiusProperty();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -151,7 +121,7 @@ public class JFXSpinner extends ProgressIndicator {
|
||||
static {
|
||||
final List<CssMetaData<? extends Styleable, ?>> styleables =
|
||||
new ArrayList<>(ProgressIndicator.getClassCssMetaData());
|
||||
Collections.addAll(styleables, RADIUS, STARTING_ANGLE);
|
||||
Collections.addAll(styleables, RADIUS);
|
||||
CHILD_STYLEABLES = List.copyOf(styleables);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +88,14 @@ public class JFXTextField extends TextField {
|
||||
useJFXContextMenu(this);
|
||||
}
|
||||
|
||||
// https://github.com/HMCL-dev/HMCL/issues/5822
|
||||
// TODO: This method may no longer be needed after we update JFXTextFieldSkin
|
||||
@Override
|
||||
protected void layoutChildren() {
|
||||
super.layoutChildren();
|
||||
this.setNeedsLayout(false);
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* *
|
||||
* Properties *
|
||||
|
||||
@@ -42,11 +42,11 @@ public final class JFXDepthManager {
|
||||
|
||||
private static final DropShadow[] depth = new DropShadow[] {
|
||||
new DropShadow(BlurType.GAUSSIAN, Color.TRANSPARENT, 0, 0, 0, 0),
|
||||
new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.12), 4, 0, 0, 1),
|
||||
new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.16), 6, 0, 0, 2),
|
||||
new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.20), 10, 0, 0, 3),
|
||||
new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.24), 14, 0, 0, 4),
|
||||
new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.28), 20, 0, 0, 6)
|
||||
new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.12), 4, 0, 0, 0),
|
||||
new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.16), 8, 0, 0, 4),
|
||||
new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.20), 12, 0, 0, 4),
|
||||
new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.24), 16, 0, 0, 4),
|
||||
new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.28), 20, 0, 0, 8)
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -309,6 +309,9 @@ public class JFXCustomColorPickerDialog extends StackPane {
|
||||
|
||||
public void setCurrentColor(Color currentColor) {
|
||||
this.currentColorProperty.set(currentColor);
|
||||
if (curvedColorPicker != null && currentColor != null) {
|
||||
curvedColorPicker.setColor(currentColor);
|
||||
}
|
||||
}
|
||||
|
||||
Color getCurrentColor() {
|
||||
|
||||
@@ -20,61 +20,38 @@
|
||||
package com.jfoenix.skins;
|
||||
|
||||
import com.jfoenix.controls.JFXProgressBar;
|
||||
import com.jfoenix.utils.JFXNodeUtils;
|
||||
import com.jfoenix.utils.TreeShowingProperty;
|
||||
import javafx.animation.*;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ProgressIndicator;
|
||||
import javafx.scene.control.skin.ProgressIndicatorSkin;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
|
||||
/// # Material Design ProgressBar Skin
|
||||
///
|
||||
/// @author Shadi Shaheen
|
||||
/// @version 2.0
|
||||
/// @since 2017-10-06
|
||||
public class JFXProgressBarSkin extends ProgressIndicatorSkin {
|
||||
public class JFXProgressBarSkin extends SkinBase<JFXProgressBar> {
|
||||
|
||||
private StackPane track;
|
||||
private StackPane secondaryBar;
|
||||
private StackPane bar;
|
||||
private double barWidth = 0;
|
||||
private double secondaryBarWidth = 0;
|
||||
private Timeline indeterminateTransition;
|
||||
private Region clip;
|
||||
private static final double DEFAULT_HEIGHT = 4;
|
||||
|
||||
private final StackPane track;
|
||||
private final StackPane bar;
|
||||
private final Rectangle clip;
|
||||
private Animation transition;
|
||||
private final TreeShowingProperty treeShowingProperty;
|
||||
private double fullWidth;
|
||||
|
||||
public JFXProgressBarSkin(JFXProgressBar bar) {
|
||||
super(bar);
|
||||
public JFXProgressBarSkin(JFXProgressBar control) {
|
||||
super(control);
|
||||
|
||||
this.treeShowingProperty = new TreeShowingProperty(bar);
|
||||
this.treeShowingProperty = new TreeShowingProperty(control);
|
||||
|
||||
bar.widthProperty().addListener(observable -> {
|
||||
updateProgress();
|
||||
updateSecondaryProgress();
|
||||
});
|
||||
|
||||
registerChangeListener(bar.progressProperty(), (obs) -> updateProgress());
|
||||
registerChangeListener(bar.secondaryProgressProperty(), obs -> updateSecondaryProgress());
|
||||
registerChangeListener(bar.visibleProperty(), obs -> updateAnimation());
|
||||
registerChangeListener(bar.parentProperty(), obs -> updateAnimation());
|
||||
registerChangeListener(bar.sceneProperty(), obs -> updateAnimation());
|
||||
|
||||
unregisterChangeListeners(treeShowingProperty);
|
||||
unregisterChangeListeners(bar.indeterminateProperty());
|
||||
|
||||
registerChangeListener(treeShowingProperty, obs -> this.updateAnimation());
|
||||
registerChangeListener(bar.indeterminateProperty(), obs -> initialize());
|
||||
|
||||
initialize();
|
||||
|
||||
getSkinnable().requestLayout();
|
||||
}
|
||||
|
||||
protected void initialize() {
|
||||
registerChangeListener(treeShowingProperty, obs -> updateProgress(false));
|
||||
registerChangeListener(control.progressProperty(), obs -> updateProgress(true));
|
||||
|
||||
track = new StackPane();
|
||||
track.getStyleClass().setAll("track");
|
||||
@@ -82,14 +59,14 @@ public class JFXProgressBarSkin extends ProgressIndicatorSkin {
|
||||
bar = new StackPane();
|
||||
bar.getStyleClass().setAll("bar");
|
||||
|
||||
secondaryBar = new StackPane();
|
||||
secondaryBar.getStyleClass().setAll("secondary-bar");
|
||||
clip = new Rectangle();
|
||||
clip.setArcWidth(DEFAULT_HEIGHT);
|
||||
clip.setArcHeight(DEFAULT_HEIGHT);
|
||||
bar.setClip(clip);
|
||||
|
||||
clip = new Region();
|
||||
clip.setBackground(new Background(new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
bar.backgroundProperty().addListener(observable -> JFXNodeUtils.updateBackground(bar.getBackground(), clip));
|
||||
getChildren().setAll(track, bar);
|
||||
|
||||
getChildren().setAll(track, secondaryBar, bar);
|
||||
getSkinnable().requestLayout();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -104,7 +81,7 @@ public class JFXProgressBarSkin extends ProgressIndicatorSkin {
|
||||
|
||||
@Override
|
||||
protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
|
||||
return topInset + bar.prefHeight(width) + bottomInset;
|
||||
return topInset + DEFAULT_HEIGHT + bottomInset;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -120,105 +97,129 @@ public class JFXProgressBarSkin extends ProgressIndicatorSkin {
|
||||
@Override
|
||||
protected void layoutChildren(double x, double y, double w, double h) {
|
||||
track.resizeRelocate(x, y, w, h);
|
||||
secondaryBar.resizeRelocate(x, y, secondaryBarWidth, h);
|
||||
bar.resizeRelocate(x, y, getSkinnable().isIndeterminate() ? w : barWidth, h);
|
||||
clip.resizeRelocate(0, 0, w, h);
|
||||
bar.resizeRelocate(x, y, w, h);
|
||||
|
||||
if (getSkinnable().isIndeterminate()) {
|
||||
createIndeterminateTimeline();
|
||||
if (JFXNodeUtils.isTreeShowing(getSkinnable())) {
|
||||
indeterminateTransition.play();
|
||||
}
|
||||
// apply clip
|
||||
bar.setClip(clip);
|
||||
} else if (indeterminateTransition != null) {
|
||||
clip.relocate(0, 0);
|
||||
clip.setWidth(0);
|
||||
clip.setHeight(h);
|
||||
clip.setTranslateX(0);
|
||||
|
||||
fullWidth = w;
|
||||
|
||||
clearAnimation();
|
||||
updateProgress(false);
|
||||
}
|
||||
|
||||
private boolean wasIndeterminate = false;
|
||||
|
||||
private void updateProgress(boolean playProgressAnimation) {
|
||||
double progress = Math.min(getSkinnable().getProgress(), 1.0);
|
||||
boolean isIndeterminate = progress < 0.0;
|
||||
boolean isTreeShowing = treeShowingProperty.get();
|
||||
|
||||
if (isIndeterminate != wasIndeterminate) {
|
||||
wasIndeterminate = isIndeterminate;
|
||||
clearAnimation();
|
||||
// remove clip
|
||||
bar.setClip(null);
|
||||
clip.setTranslateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateSecondaryProgress() {
|
||||
final JFXProgressBar control = (JFXProgressBar) getSkinnable();
|
||||
secondaryBarWidth = ((int) (control.getWidth() - snappedLeftInset() - snappedRightInset()) * 2
|
||||
* Math.min(1, Math.max(0, control.getSecondaryProgress()))) / 2.0F;
|
||||
control.requestLayout();
|
||||
}
|
||||
|
||||
boolean wasIndeterminate = false;
|
||||
|
||||
protected void pauseTimeline(boolean pause) {
|
||||
if (getSkinnable().isIndeterminate()) {
|
||||
if (indeterminateTransition == null) {
|
||||
createIndeterminateTimeline();
|
||||
if (isIndeterminate) { // indeterminate
|
||||
if (isTreeShowing) {
|
||||
if (transition == null) {
|
||||
transition = createIndeterminateTransition();
|
||||
transition.playFromStart();
|
||||
} else {
|
||||
transition.play();
|
||||
}
|
||||
} else if (transition != null) {
|
||||
transition.pause();
|
||||
}
|
||||
if (pause) {
|
||||
indeterminateTransition.pause();
|
||||
} else { // determinate
|
||||
clearAnimation();
|
||||
if (isTreeShowing && playProgressAnimation
|
||||
&& AnimationUtils.isAnimationEnabled()
|
||||
&& getSkinnable().isSmoothProgress()) {
|
||||
transition = createDeterminateTransition(progress);
|
||||
transition.playFromStart();
|
||||
} else {
|
||||
indeterminateTransition.play();
|
||||
clip.setWidth(computeBarWidth(progress));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAnimation() {
|
||||
final boolean isTreeShowing = treeShowingProperty.get();
|
||||
if (indeterminateTransition != null) {
|
||||
pauseTimeline(!isTreeShowing);
|
||||
} else if (isTreeShowing) {
|
||||
createIndeterminateTimeline();
|
||||
}
|
||||
private static final Duration INDETERMINATE_DURATION = Duration.seconds(1);
|
||||
|
||||
private Transition createIndeterminateTransition() {
|
||||
double minWidth = 0;
|
||||
double maxWidth = fullWidth * 0.4;
|
||||
Transition transition = new Transition() {
|
||||
{
|
||||
setInterpolator(Interpolator.LINEAR);
|
||||
setCycleDuration(INDETERMINATE_DURATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void interpolate(double frac) {
|
||||
double currentWidth;
|
||||
|
||||
if (frac <= 0.5) {
|
||||
currentWidth = Interpolator.EASE_IN.interpolate(minWidth, maxWidth, frac / 0.5);
|
||||
} else {
|
||||
currentWidth = Interpolator.EASE_OUT.interpolate(maxWidth, minWidth, (frac - 0.5) / 0.5);
|
||||
}
|
||||
|
||||
double targetCenter;
|
||||
if (frac <= 0.1) {
|
||||
targetCenter = 0.0;
|
||||
} else if (frac >= 0.9) {
|
||||
targetCenter = fullWidth;
|
||||
} else {
|
||||
targetCenter = ((frac - 0.1) / 0.8) * fullWidth;
|
||||
}
|
||||
|
||||
clip.setWidth(currentWidth);
|
||||
clip.setTranslateX(targetCenter - currentWidth / 2.0);
|
||||
}
|
||||
};
|
||||
|
||||
transition.setCycleCount(Timeline.INDEFINITE);
|
||||
return transition;
|
||||
}
|
||||
|
||||
private void updateProgress() {
|
||||
final ProgressIndicator control = getSkinnable();
|
||||
final boolean isIndeterminate = control.isIndeterminate();
|
||||
if (!(isIndeterminate && wasIndeterminate)) {
|
||||
barWidth = ((int) (control.getWidth() - snappedLeftInset() - snappedRightInset()) * 2
|
||||
* Math.min(1, Math.max(0, control.getProgress()))) / 2.0F;
|
||||
control.requestLayout();
|
||||
}
|
||||
wasIndeterminate = isIndeterminate;
|
||||
}
|
||||
private static final Duration DETERMINATE_DURATION = Duration.seconds(0.2);
|
||||
|
||||
private void createIndeterminateTimeline() {
|
||||
if (indeterminateTransition != null) {
|
||||
clearAnimation();
|
||||
}
|
||||
double dur = 1;
|
||||
ProgressIndicator control = getSkinnable();
|
||||
final double w = control.getWidth() - (snappedLeftInset() + snappedRightInset());
|
||||
indeterminateTransition = new Timeline(new KeyFrame(
|
||||
Duration.ZERO,
|
||||
new KeyValue(clip.scaleXProperty(), 0.0, Interpolator.EASE_IN),
|
||||
new KeyValue(clip.translateXProperty(), -w / 2, Interpolator.LINEAR)
|
||||
),
|
||||
new KeyFrame(
|
||||
Duration.seconds(0.5 * dur),
|
||||
new KeyValue(clip.scaleXProperty(), 0.4, Interpolator.LINEAR)
|
||||
),
|
||||
new KeyFrame(
|
||||
Duration.seconds(0.9 * dur),
|
||||
new KeyValue(clip.translateXProperty(), w / 2, Interpolator.LINEAR)
|
||||
),
|
||||
new KeyFrame(
|
||||
Duration.seconds(1 * dur),
|
||||
new KeyValue(clip.scaleXProperty(), 0.0, Interpolator.EASE_OUT)
|
||||
));
|
||||
indeterminateTransition.setCycleCount(Timeline.INDEFINITE);
|
||||
private Timeline createDeterminateTransition(double targetProgress) {
|
||||
Timeline timeline = new Timeline(
|
||||
new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(clip.widthProperty(), clip.getWidth(), Interpolator.EASE_OUT)),
|
||||
new KeyFrame(DETERMINATE_DURATION,
|
||||
new KeyValue(clip.widthProperty(), computeBarWidth(targetProgress), Interpolator.EASE_OUT))
|
||||
);
|
||||
timeline.setOnFinished(e -> {
|
||||
if (transition == timeline) {
|
||||
transition = null;
|
||||
}
|
||||
});
|
||||
return timeline;
|
||||
}
|
||||
|
||||
private void clearAnimation() {
|
||||
indeterminateTransition.stop();
|
||||
indeterminateTransition.getKeyFrames().clear();
|
||||
indeterminateTransition = null;
|
||||
if (transition != null) {
|
||||
transition.stop();
|
||||
transition = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
treeShowingProperty.dispose();
|
||||
if (indeterminateTransition != null) {
|
||||
clearAnimation();
|
||||
}
|
||||
clearAnimation();
|
||||
}
|
||||
|
||||
private double computeBarWidth(double progress) {
|
||||
assert progress >= 0 && progress <= 1;
|
||||
double barWidth = ((int) fullWidth * 2 * progress) / 2.0;
|
||||
return progress > 0 ? Math.max(barWidth, DEFAULT_HEIGHT) : barWidth;
|
||||
}
|
||||
}
|
||||
@@ -20,25 +20,24 @@
|
||||
package com.jfoenix.skins;
|
||||
|
||||
import com.jfoenix.controls.JFXSpinner;
|
||||
import com.jfoenix.utils.JFXNodeUtils;
|
||||
import com.jfoenix.utils.TreeShowingProperty;
|
||||
import javafx.animation.Interpolator;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.control.ProgressIndicator;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.Arc;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.scene.shape.StrokeLineCap;
|
||||
import javafx.scene.text.Font;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/// JFXSpinner material design skin
|
||||
///
|
||||
@@ -47,10 +46,7 @@ import javafx.util.Duration;
|
||||
/// @since 2017-09-25
|
||||
public class JFXSpinnerSkin extends SkinBase<JFXSpinner> {
|
||||
|
||||
private static final Color GREEN_COLOR = Color.valueOf("#0F9D58");
|
||||
private static final Color RED_COLOR = Color.valueOf("#db4437");
|
||||
private static final Color YELLOW_COLOR = Color.valueOf("#f4b400");
|
||||
private static final Color BLUE_COLOR = Color.valueOf("#4285f4");
|
||||
private static final double DEFAULT_STROKE_WIDTH = 4;
|
||||
|
||||
private JFXSpinner control;
|
||||
private final TreeShowingProperty treeShowingProperty;
|
||||
@@ -61,110 +57,62 @@ public class JFXSpinnerSkin extends SkinBase<JFXSpinner> {
|
||||
private Arc track;
|
||||
private final StackPane arcPane;
|
||||
private final Rectangle fillRect;
|
||||
private double arcLength = -1;
|
||||
private final Text text;
|
||||
|
||||
private final double startingAngle;
|
||||
|
||||
public JFXSpinnerSkin(JFXSpinner control) {
|
||||
super(control);
|
||||
|
||||
this.control = control;
|
||||
this.treeShowingProperty = new TreeShowingProperty(control);
|
||||
this.startingAngle = control.getStartingAngle();
|
||||
|
||||
arc = new Arc();
|
||||
arc.setManaged(false);
|
||||
arc.setStartAngle(0);
|
||||
arc.setLength(180);
|
||||
arc.getStyleClass().setAll("arc");
|
||||
arc.setFill(Color.TRANSPARENT);
|
||||
arc.setStrokeWidth(3);
|
||||
arc.setStrokeWidth(DEFAULT_STROKE_WIDTH);
|
||||
arc.setStrokeLineCap(StrokeLineCap.ROUND);
|
||||
|
||||
track = new Arc();
|
||||
track.setManaged(false);
|
||||
track.setStartAngle(0);
|
||||
track.setLength(360);
|
||||
track.setStrokeWidth(3);
|
||||
track.setStrokeWidth(DEFAULT_STROKE_WIDTH);
|
||||
track.getStyleClass().setAll("track");
|
||||
track.setFill(Color.TRANSPARENT);
|
||||
|
||||
fillRect = new Rectangle();
|
||||
fillRect.setFill(Color.TRANSPARENT);
|
||||
text = new Text();
|
||||
text.setStyle("-fx-font-size:null");
|
||||
text.getStyleClass().setAll("text", "percentage");
|
||||
final Group group = new Group(fillRect, track, arc, text);
|
||||
final Group group = new Group(fillRect, track, arc);
|
||||
group.setManaged(false);
|
||||
arcPane = new StackPane(group);
|
||||
arcPane.setPrefSize(50, 50);
|
||||
getChildren().setAll(arcPane);
|
||||
|
||||
// register listeners
|
||||
registerChangeListener(control.indeterminateProperty(), obs -> initialize());
|
||||
registerChangeListener(control.progressProperty(), obs -> updateProgress());
|
||||
registerChangeListener(treeShowingProperty, obs -> updateAnimation());
|
||||
registerChangeListener(control.sceneProperty(), obs -> updateAnimation());
|
||||
registerChangeListener(treeShowingProperty, obs -> updateProgress());
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
if (getSkinnable().isIndeterminate()) {
|
||||
if (timeline == null) {
|
||||
createTransition();
|
||||
if (JFXNodeUtils.isTreeShowing(getSkinnable())) {
|
||||
private void updateProgress() {
|
||||
double progress = Double.min(getSkinnable().getProgress(), 1.0);
|
||||
if (progress < 0) { // indeterminate
|
||||
boolean treeShowing = treeShowingProperty.get();
|
||||
if (treeShowing) {
|
||||
if (timeline == null) {
|
||||
timeline = createTransition();
|
||||
timeline.playFromStart();
|
||||
} else {
|
||||
timeline.play();
|
||||
}
|
||||
} else if (timeline != null) {
|
||||
timeline.pause();
|
||||
}
|
||||
} else {
|
||||
} else { // determinate
|
||||
clearAnimation();
|
||||
arc.setStartAngle(90);
|
||||
updateProgress();
|
||||
}
|
||||
}
|
||||
|
||||
private KeyFrame[] getKeyFrames(double angle, double duration, Paint color) {
|
||||
KeyFrame[] frames = new KeyFrame[4];
|
||||
frames[0] = new KeyFrame(Duration.seconds(duration),
|
||||
new KeyValue(arc.lengthProperty(), 5, Interpolator.LINEAR),
|
||||
new KeyValue(arc.startAngleProperty(),
|
||||
angle + 45 + control.getStartingAngle(),
|
||||
Interpolator.LINEAR));
|
||||
frames[1] = new KeyFrame(Duration.seconds(duration + 0.4),
|
||||
new KeyValue(arc.lengthProperty(), 250, Interpolator.LINEAR),
|
||||
new KeyValue(arc.startAngleProperty(),
|
||||
angle + 90 + control.getStartingAngle(),
|
||||
Interpolator.LINEAR));
|
||||
frames[2] = new KeyFrame(Duration.seconds(duration + 0.7),
|
||||
new KeyValue(arc.lengthProperty(), 250, Interpolator.LINEAR),
|
||||
new KeyValue(arc.startAngleProperty(),
|
||||
angle + 135 + control.getStartingAngle(),
|
||||
Interpolator.LINEAR));
|
||||
frames[3] = new KeyFrame(Duration.seconds(duration + 1.1),
|
||||
new KeyValue(arc.lengthProperty(), 5, Interpolator.LINEAR),
|
||||
new KeyValue(arc.startAngleProperty(),
|
||||
angle + 435 + control.getStartingAngle(),
|
||||
Interpolator.LINEAR),
|
||||
new KeyValue(arc.strokeProperty(), color, Interpolator.EASE_BOTH));
|
||||
return frames;
|
||||
}
|
||||
|
||||
private void pauseTimeline(boolean pause) {
|
||||
if (getSkinnable().isIndeterminate()) {
|
||||
if (timeline == null) {
|
||||
createTransition();
|
||||
}
|
||||
if (pause) {
|
||||
timeline.pause();
|
||||
} else {
|
||||
timeline.play();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAnimation() {
|
||||
final boolean isTreeShowing = treeShowingProperty.get();
|
||||
if (timeline != null) {
|
||||
pauseTimeline(!isTreeShowing);
|
||||
} else if (isTreeShowing) {
|
||||
createTransition();
|
||||
arc.setLength(-360 * progress);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,7 +162,7 @@ public class JFXSpinnerSkin extends SkinBase<JFXSpinner> {
|
||||
@Override
|
||||
protected void layoutChildren(double contentX, double contentY, double contentWidth, double contentHeight) {
|
||||
final double strokeWidth = arc.getStrokeWidth();
|
||||
final double radius = Math.min(contentWidth, contentHeight) / 2 - strokeWidth / 2;
|
||||
double radius = control.getRadius();
|
||||
final double arcSize = snapSizeX(radius * 2 + strokeWidth);
|
||||
|
||||
arcPane.resizeRelocate((contentWidth - arcSize) / 2 + 1, (contentHeight - arcSize) / 2 + 1, arcSize, arcSize);
|
||||
@@ -224,21 +172,9 @@ public class JFXSpinnerSkin extends SkinBase<JFXSpinner> {
|
||||
fillRect.setHeight(arcSize);
|
||||
|
||||
if (!isValid) {
|
||||
initialize();
|
||||
updateProgress();
|
||||
isValid = true;
|
||||
}
|
||||
|
||||
if (!getSkinnable().isIndeterminate()) {
|
||||
arc.setLength(arcLength);
|
||||
if (text.isVisible()) {
|
||||
final double progress = control.getProgress();
|
||||
int intProgress = (int) Math.round(progress * 100.0);
|
||||
Font font = text.getFont();
|
||||
text.setFont(Font.font(font.getFamily(), radius / 1.7));
|
||||
text.setText((progress > 1 ? 100 : intProgress) + "%");
|
||||
text.relocate((arcSize - text.getLayoutBounds().getWidth()) / 2, (arcSize - text.getLayoutBounds().getHeight()) / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateArcLayout(double radius, double arcSize) {
|
||||
@@ -254,66 +190,64 @@ public class JFXSpinnerSkin extends SkinBase<JFXSpinner> {
|
||||
track.setStrokeWidth(arc.getStrokeWidth());
|
||||
}
|
||||
|
||||
boolean wasIndeterminate = false;
|
||||
|
||||
protected void updateProgress() {
|
||||
final ProgressIndicator control = getSkinnable();
|
||||
final boolean isIndeterminate = control.isIndeterminate();
|
||||
if (!(isIndeterminate && wasIndeterminate)) {
|
||||
arcLength = -360 * control.getProgress();
|
||||
control.requestLayout();
|
||||
}
|
||||
wasIndeterminate = isIndeterminate;
|
||||
}
|
||||
|
||||
private void createTransition() {
|
||||
if (!getSkinnable().isIndeterminate()) return;
|
||||
final Paint initialColor = arc.getStroke();
|
||||
if (initialColor == null) {
|
||||
arc.setStroke(BLUE_COLOR);
|
||||
}
|
||||
|
||||
KeyFrame[] blueFrame = getKeyFrames(0, 0, initialColor == null ? BLUE_COLOR : initialColor);
|
||||
KeyFrame[] redFrame = getKeyFrames(450, 1.4, initialColor == null ? RED_COLOR : initialColor);
|
||||
KeyFrame[] yellowFrame = getKeyFrames(900, 2.8, initialColor == null ? YELLOW_COLOR : initialColor);
|
||||
KeyFrame[] greenFrame = getKeyFrames(1350, 4.2, initialColor == null ? GREEN_COLOR : initialColor);
|
||||
|
||||
KeyFrame endingFrame = new KeyFrame(Duration.seconds(5.6),
|
||||
private void addKeyFrames(List<KeyFrame> frames, double angle, double duration) {
|
||||
frames.add(new KeyFrame(Duration.seconds(duration),
|
||||
new KeyValue(arc.lengthProperty(), 5, Interpolator.LINEAR),
|
||||
new KeyValue(arc.startAngleProperty(),
|
||||
1845 + control.getStartingAngle(),
|
||||
Interpolator.LINEAR));
|
||||
angle + 45 + startingAngle,
|
||||
Interpolator.LINEAR)));
|
||||
frames.add(new KeyFrame(Duration.seconds(duration + 0.4),
|
||||
new KeyValue(arc.lengthProperty(), 250, Interpolator.LINEAR),
|
||||
new KeyValue(arc.startAngleProperty(),
|
||||
angle + 90 + startingAngle,
|
||||
Interpolator.LINEAR)));
|
||||
frames.add(new KeyFrame(Duration.seconds(duration + 0.7),
|
||||
new KeyValue(arc.lengthProperty(), 250, Interpolator.LINEAR),
|
||||
new KeyValue(arc.startAngleProperty(),
|
||||
angle + 135 + startingAngle,
|
||||
Interpolator.LINEAR)));
|
||||
frames.add(new KeyFrame(Duration.seconds(duration + 1.1),
|
||||
new KeyValue(arc.lengthProperty(), 5, Interpolator.LINEAR),
|
||||
new KeyValue(arc.startAngleProperty(),
|
||||
angle + 435 + startingAngle,
|
||||
Interpolator.LINEAR)));
|
||||
}
|
||||
|
||||
if (timeline != null) {
|
||||
timeline.stop();
|
||||
timeline.getKeyFrames().clear();
|
||||
private Timeline createTransition() {
|
||||
Timeline timeline;
|
||||
if (AnimationUtils.isAnimationEnabled()) {
|
||||
var keyFrames = new ArrayList<KeyFrame>(17);
|
||||
addKeyFrames(keyFrames, 0, 0);
|
||||
addKeyFrames(keyFrames, 450, 1.4);
|
||||
addKeyFrames(keyFrames, 900, 2.8);
|
||||
addKeyFrames(keyFrames, 1350, 4.2);
|
||||
keyFrames.add(new KeyFrame(Duration.seconds(5.6),
|
||||
new KeyValue(arc.lengthProperty(), 5, Interpolator.LINEAR),
|
||||
new KeyValue(arc.startAngleProperty(),
|
||||
1845 + startingAngle,
|
||||
Interpolator.LINEAR)));
|
||||
|
||||
timeline = new Timeline();
|
||||
timeline.getKeyFrames().setAll(keyFrames);
|
||||
} else {
|
||||
final double arcLength = 250;
|
||||
timeline = new Timeline(
|
||||
new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(arc.startAngleProperty(), 45 + startingAngle, Interpolator.LINEAR),
|
||||
new KeyValue(arc.lengthProperty(), arcLength, Interpolator.DISCRETE)),
|
||||
new KeyFrame(Duration.seconds(1.2),
|
||||
new KeyValue(arc.startAngleProperty(), 45 + 360 + startingAngle, Interpolator.LINEAR),
|
||||
new KeyValue(arc.lengthProperty(), arcLength, Interpolator.DISCRETE))
|
||||
);
|
||||
}
|
||||
timeline = new Timeline(blueFrame[0],
|
||||
blueFrame[1],
|
||||
blueFrame[2],
|
||||
blueFrame[3],
|
||||
redFrame[0],
|
||||
redFrame[1],
|
||||
redFrame[2],
|
||||
redFrame[3],
|
||||
yellowFrame[0],
|
||||
yellowFrame[1],
|
||||
yellowFrame[2],
|
||||
yellowFrame[3],
|
||||
greenFrame[0],
|
||||
greenFrame[1],
|
||||
greenFrame[2],
|
||||
greenFrame[3],
|
||||
endingFrame);
|
||||
|
||||
timeline.setCycleCount(Timeline.INDEFINITE);
|
||||
timeline.setDelay(Duration.ZERO);
|
||||
timeline.playFromStart();
|
||||
return timeline;
|
||||
}
|
||||
|
||||
private void clearAnimation() {
|
||||
if (timeline != null) {
|
||||
timeline.stop();
|
||||
timeline.getKeyFrames().clear();
|
||||
timeline = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,12 +21,16 @@ import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
import org.jackhuang.hmcl.launch.DefaultLauncher;
|
||||
import org.jackhuang.hmcl.launch.ProcessListener;
|
||||
import org.jackhuang.hmcl.util.NativePatcher;
|
||||
import org.jackhuang.hmcl.util.i18n.LocaleUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.JarUtils;
|
||||
import org.jackhuang.hmcl.util.platform.CommandBuilder;
|
||||
import org.jackhuang.hmcl.util.platform.ManagedProcess;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
@@ -150,4 +154,60 @@ public final class HMCLGameLauncher extends DefaultLauncher {
|
||||
generateOptionsTxt();
|
||||
super.makeLaunchScript(scriptFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void appendJvmArgs(CommandBuilder result) {
|
||||
super.appendJvmArgs(result);
|
||||
|
||||
if (config().getAllowAutoAgent()
|
||||
&& !options.isNoGeneratedJVMArgs()
|
||||
&& !options.isNoGeneratedOptimizingJVMArgs()
|
||||
&& NativePatcher.needPatchMemoryUtil(version, options.getJava().getParsedVersion())) {
|
||||
LOG.info("Attempting to patch game with lwjgl-unsafe-agent");
|
||||
try {
|
||||
result.add("-javaagent:" + extractLwjglUnsafeAgent());
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to extract lwjgl-unsafe-agent", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Path extractLwjglUnsafeAgent() throws IOException {
|
||||
String agentVersion = JarUtils.getAttribute("hmcl.lwjgl-unsafe-agent.version", null);
|
||||
if (agentVersion == null) {
|
||||
throw new IOException("Missing hmcl.lwjgl-unsafe-agent.version attribute");
|
||||
}
|
||||
|
||||
Library library = new Library(new Artifact("org.glavo", "lwjgl-unsafe-agent", agentVersion));
|
||||
String fileName = library.getArtifact().getFileName();
|
||||
|
||||
Path agentPath = repository.getLibraryFile(version, library).toAbsolutePath().normalize();
|
||||
if (agentPath.toString().contains("=")) {
|
||||
throw new IOException("Invalid library path: " + agentPath);
|
||||
}
|
||||
|
||||
byte[] bytes;
|
||||
try (InputStream input = DefaultLauncher.class.getResourceAsStream("/assets/" + fileName)) {
|
||||
if (input == null) {
|
||||
throw new IOException("/assets/" + fileName + " not found");
|
||||
}
|
||||
|
||||
bytes = input.readAllBytes();
|
||||
}
|
||||
|
||||
if (Files.isRegularFile(agentPath)) {
|
||||
try {
|
||||
if (Files.size(agentPath) == bytes.length) {
|
||||
return agentPath;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to check size of " + agentPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
Files.createDirectories(agentPath.getParent());
|
||||
FileUtils.saveSafely(agentPath, output -> output.write(bytes));
|
||||
return agentPath;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -65,6 +65,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
import static javafx.application.Platform.runLater;
|
||||
import static javafx.application.Platform.setImplicitExit;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.DataSizeUnit.MEGABYTES;
|
||||
import static org.jackhuang.hmcl.util.Lang.resolveException;
|
||||
@@ -75,6 +76,8 @@ import static org.jackhuang.hmcl.util.platform.Platform.isCompatibleWithX86Java;
|
||||
|
||||
public final class LauncherHelper {
|
||||
|
||||
private static final String LWJGL_3_4_1_TIP = "lwjgl3.4.1-ffm";
|
||||
|
||||
private final Profile profile;
|
||||
private Account account;
|
||||
private final String selectedVersion;
|
||||
@@ -197,6 +200,28 @@ public final class LauncherHelper {
|
||||
);
|
||||
}).withStage("launch.state.dependencies")
|
||||
.thenComposeAsync(() -> gameVersion.map(s -> new GameVerificationFixTask(dependencyManager, s, version.get())).orElse(null))
|
||||
.thenComposeAsync(() -> {
|
||||
if (config().getAllowAutoAgent()
|
||||
|| setting.isNoJVMArgs()
|
||||
|| setting.isNoOptimizingJVMArgs()
|
||||
|| Boolean.TRUE.equals(config().getShownTips().get(LWJGL_3_4_1_TIP))
|
||||
|| !NativePatcher.needPatchMemoryUtil(version.get(), javaVersionRef.get().getParsedVersion())) {
|
||||
return Task.completed(null);
|
||||
} else {
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
runInFX(() -> {
|
||||
Controllers.confirm(i18n("launch.advice.lwjgl_3_4_1"), i18n("launch.advice.lwjgl_3_4_1.title"), MessageType.QUESTION, () -> {
|
||||
config().getShownTips().put(LWJGL_3_4_1_TIP, true);
|
||||
config().setAllowAutoAgent(true);
|
||||
future.complete(null);
|
||||
}, () -> {
|
||||
config().getShownTips().put(LWJGL_3_4_1_TIP, true);
|
||||
future.complete(null);
|
||||
});
|
||||
});
|
||||
return Task.fromCompletableFuture(future);
|
||||
}
|
||||
})
|
||||
.thenComposeAsync(() -> logIn(account).withStage("launch.state.logging_in"))
|
||||
.thenComposeAsync(authInfo -> Task.supplyAsync(() -> {
|
||||
LaunchOptions.Builder launchOptionsBuilder = repository.getLaunchOptions(
|
||||
@@ -387,7 +412,16 @@ public final class LauncherHelper {
|
||||
if (setting.getJavaVersionType() == JavaVersionType.VERSION) {
|
||||
try {
|
||||
int targetJavaVersionMajor = Integer.parseInt(setting.getJavaVersion());
|
||||
GameJavaVersion minimumJavaVersion = GameJavaVersion.getMinimumJavaVersion(gameVersion);
|
||||
GameJavaVersion minimumJavaVersion = null;
|
||||
if (gameVersion.compareTo("1.12.2") == 0) {
|
||||
Optional<String> cleanroomVersion = analyzer.getVersion(LibraryAnalyzer.LibraryType.CLEANROOM);
|
||||
if (cleanroomVersion.isPresent()) {
|
||||
minimumJavaVersion = GameJavaVersion.getCleanroomJavaVersion(cleanroomVersion.get());
|
||||
}
|
||||
}
|
||||
|
||||
if (minimumJavaVersion == null)
|
||||
minimumJavaVersion = GameJavaVersion.getMinimumJavaVersion(gameVersion);
|
||||
|
||||
if (minimumJavaVersion != null && targetJavaVersionMajor < minimumJavaVersion.majorVersion()) {
|
||||
Controllers.dialog(
|
||||
@@ -402,8 +436,17 @@ public final class LauncherHelper {
|
||||
targetJavaVersion = GameJavaVersion.get(targetJavaVersionMajor);
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
} else
|
||||
targetJavaVersion = version.getJavaVersion();
|
||||
} else {
|
||||
if (gameVersion.compareTo("1.12.2") == 0) {
|
||||
Optional<String> cleanroomVersion = analyzer.getVersion(LibraryAnalyzer.LibraryType.CLEANROOM);
|
||||
if (cleanroomVersion.isPresent()) {
|
||||
targetJavaVersion = GameJavaVersion.getCleanroomJavaVersion(cleanroomVersion.get());
|
||||
}
|
||||
}
|
||||
|
||||
if (targetJavaVersion == null)
|
||||
targetJavaVersion = version.getJavaVersion();
|
||||
}
|
||||
|
||||
if (targetJavaVersion != null && supportedVersions.contains(targetJavaVersion)) {
|
||||
downloadJava(targetJavaVersion, profile)
|
||||
@@ -433,7 +476,7 @@ public final class LauncherHelper {
|
||||
if (java != null) {
|
||||
for (JavaVersionConstraint constraint : JavaVersionConstraint.ALL) {
|
||||
if (constraint.appliesToVersion(gameVersion, version, java, analyzer)) {
|
||||
if (!constraint.checkJava(gameVersion, version, java)) {
|
||||
if (!constraint.checkJava(gameVersion, version, java, analyzer)) {
|
||||
if (constraint.isMandatory()) {
|
||||
violatedMandatoryConstraints.add(constraint);
|
||||
} else {
|
||||
@@ -468,9 +511,14 @@ public final class LauncherHelper {
|
||||
return result;
|
||||
} else {
|
||||
GameJavaVersion gameJavaVersion;
|
||||
if (violatedMandatoryConstraints.contains(JavaVersionConstraint.CLEANROOM_JAVA_21))
|
||||
gameJavaVersion = GameJavaVersion.JAVA_21;
|
||||
else if (violatedMandatoryConstraints.contains(JavaVersionConstraint.GAME_JSON))
|
||||
if (violatedMandatoryConstraints.contains(JavaVersionConstraint.CLEANROOM)) {
|
||||
String cleanroomVersion = analyzer.getVersion(LibraryAnalyzer.LibraryType.CLEANROOM)
|
||||
.orElse("");
|
||||
|
||||
gameJavaVersion = !cleanroomVersion.isEmpty()
|
||||
? GameJavaVersion.getCleanroomJavaVersion(cleanroomVersion)
|
||||
: GameJavaVersion.JAVA_21;
|
||||
} else if (violatedMandatoryConstraints.contains(JavaVersionConstraint.GAME_JSON))
|
||||
gameJavaVersion = version.getJavaVersion();
|
||||
else if (violatedMandatoryConstraints.contains(JavaVersionConstraint.VANILLA))
|
||||
gameJavaVersion = GameJavaVersion.getMinimumJavaVersion(gameVersion);
|
||||
@@ -560,9 +608,14 @@ public final class LauncherHelper {
|
||||
case MODDED_JAVA_21:
|
||||
suggestions.add(i18n("launch.advice.modded_java", 21, gameVersion));
|
||||
break;
|
||||
case CLEANROOM_JAVA_21:
|
||||
suggestions.add(i18n("launch.advice.cleanroom"));
|
||||
case CLEANROOM: {
|
||||
String cleanroomVersion = analyzer.getVersion(LibraryAnalyzer.LibraryType.CLEANROOM).orElse("");
|
||||
if (!cleanroomVersion.isEmpty())
|
||||
suggestions.add(i18n("launch.advice.cleanroom", GameJavaVersion.getCleanroomJavaVersion(cleanroomVersion).majorVersion(), cleanroomVersion));
|
||||
else
|
||||
suggestions.add(i18n("launch.advice.cleanroom", 21, ""));
|
||||
break;
|
||||
}
|
||||
case VANILLA_JAVA_8_51:
|
||||
suggestions.add(i18n("launch.advice.java8_51_1_13"));
|
||||
break;
|
||||
|
||||
@@ -115,8 +115,8 @@ public abstract class LocalizedRemoteModRepository implements RemoteModRepositor
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoteMod getModById(String id) throws IOException {
|
||||
return getBackedRemoteModRepository().getModById(id);
|
||||
public RemoteMod getModById(DownloadProvider downloadProvider, String id) throws IOException {
|
||||
return getBackedRemoteModRepository().getModById(downloadProvider, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -125,7 +125,7 @@ public abstract class LocalizedRemoteModRepository implements RemoteModRepositor
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<RemoteMod.Version> getRemoteVersionsById(String id) throws IOException {
|
||||
return getBackedRemoteModRepository().getRemoteVersionsById(id);
|
||||
public Stream<RemoteMod.Version> getRemoteVersionsById(DownloadProvider downloadProvider, String id) throws IOException {
|
||||
return getBackedRemoteModRepository().getRemoteVersionsById(downloadProvider, id);
|
||||
}
|
||||
}
|
||||
@@ -304,7 +304,7 @@ public final class JavaManager {
|
||||
|
||||
for (JavaVersionConstraint constraint : JavaVersionConstraint.ALL) {
|
||||
if (constraint.appliesToVersion(gameVersion, version, java, analyzer)) {
|
||||
if (!constraint.checkJava(gameVersion, version, java)) {
|
||||
if (!constraint.checkJava(gameVersion, version, java, analyzer)) {
|
||||
if (constraint.isMandatory()) {
|
||||
violationMandatory = true;
|
||||
} else {
|
||||
|
||||
@@ -222,6 +222,36 @@ public final class Config extends ObservableSetting {
|
||||
this.acceptPreviewUpdate.set(acceptPreviewUpdate);
|
||||
}
|
||||
|
||||
@SerializedName("disableAutoShowUpdateDialog")
|
||||
private final BooleanProperty disableAutoShowUpdateDialog = new SimpleBooleanProperty(false);
|
||||
|
||||
public BooleanProperty disableAutoShowUpdateDialogProperty() {
|
||||
return disableAutoShowUpdateDialog;
|
||||
}
|
||||
|
||||
public boolean isDisableAutoShowUpdateDialog() {
|
||||
return disableAutoShowUpdateDialog.get();
|
||||
}
|
||||
|
||||
public void setDisableAutoShowUpdateDialog(boolean disableAutoShowUpdateDialog) {
|
||||
this.disableAutoShowUpdateDialog.set(disableAutoShowUpdateDialog);
|
||||
}
|
||||
|
||||
@SerializedName("disableAprilFools")
|
||||
private final BooleanProperty disableAprilFools = new SimpleBooleanProperty(false);
|
||||
|
||||
public BooleanProperty disableAprilFoolsProperty() {
|
||||
return disableAprilFools;
|
||||
}
|
||||
|
||||
public boolean isDisableAprilFools() {
|
||||
return disableAprilFools.get();
|
||||
}
|
||||
|
||||
public void setDisableAprilFools(boolean disableAprilFools) {
|
||||
this.disableAprilFools.set(disableAprilFools);
|
||||
}
|
||||
|
||||
@SerializedName("shownTips")
|
||||
private final ObservableMap<String, Object> shownTips = FXCollections.observableHashMap();
|
||||
|
||||
@@ -659,6 +689,21 @@ public final class Config extends ObservableSetting {
|
||||
this.disableAutoGameOptions.set(disableAutoGameOptions);
|
||||
}
|
||||
|
||||
@SerializedName("allowAutoAgent")
|
||||
private final BooleanProperty allowAutoAgent = new SimpleBooleanProperty(false);
|
||||
|
||||
public BooleanProperty allowAutoAgentProperty() {
|
||||
return allowAutoAgent;
|
||||
}
|
||||
|
||||
public boolean getAllowAutoAgent() {
|
||||
return allowAutoAgent.get();
|
||||
}
|
||||
|
||||
public void setAllowAutoAgent(boolean allowAutoAgent) {
|
||||
this.allowAutoAgent.set(allowAutoAgent);
|
||||
}
|
||||
|
||||
// Accounts
|
||||
|
||||
@SerializedName("authlibInjectorServers")
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.jfoenix.validation.base.ValidatorBase;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.WeakInvalidationListener;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
@@ -64,12 +65,16 @@ import org.jackhuang.hmcl.ui.versions.GameListPage;
|
||||
import org.jackhuang.hmcl.ui.versions.VersionPage;
|
||||
import org.jackhuang.hmcl.ui.versions.Versions;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.i18n.SupportedLocale;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@@ -82,6 +87,7 @@ public final class Controllers {
|
||||
public static final String JAVA_VERSION_TIP = "javaVersion";
|
||||
public static final String JAVA_INTERPRETED_MODE_TIP = "javaInterpretedMode";
|
||||
public static final String SOFTWARE_RENDERING = "softwareRendering";
|
||||
public static final String APRIL_FOOLS = "aprilFools";
|
||||
|
||||
public static final int MIN_WIDTH = 800 + 2 + 16; // bg width + border width*2 + shadow width*2
|
||||
public static final int MIN_HEIGHT = 450 + 2 + 40 + 16; // bg height + border width*2 + toolbar height + shadow width*2
|
||||
@@ -225,6 +231,8 @@ public final class Controllers {
|
||||
public static void initialize(Stage stage) {
|
||||
LOG.info("Start initializing application");
|
||||
|
||||
LOG.info("April Fools: " + AprilFools.isEnabled());
|
||||
|
||||
if (System.getProperty("prism.lcdtext") == null) {
|
||||
String fontAntiAliasing = globalConfig().getFontAntiAliasing();
|
||||
if ("lcd".equalsIgnoreCase(fontAntiAliasing)) {
|
||||
@@ -432,6 +440,53 @@ public final class Controllers {
|
||||
agreementPane.setActions(agreementLink, yesButton, noButton);
|
||||
Controllers.dialog(agreementPane);
|
||||
}
|
||||
|
||||
aprilFools:
|
||||
if (AprilFools.isEnabled()) {
|
||||
int currentYear = LocalDate.now().getYear();
|
||||
if (config().getShownTips().get(APRIL_FOOLS) instanceof Number year && year.intValue() >= currentYear)
|
||||
break aprilFools;
|
||||
|
||||
if (!I18n.getLocale().getLocale().getLanguage().equals("zh"))
|
||||
break aprilFools;
|
||||
|
||||
SupportedLocale lzh = SupportedLocale.getSupportedLocales().stream()
|
||||
.filter(locale -> "lzh".equals(locale.getName()))
|
||||
.findFirst().orElse(null);
|
||||
|
||||
if (lzh == null) {
|
||||
LOG.warning("No supported locale found for lzh");
|
||||
break aprilFools;
|
||||
}
|
||||
|
||||
Runnable updateShowTips = () -> config().getShownTips().put(APRIL_FOOLS, currentYear);
|
||||
|
||||
Controllers.confirmWithCountdown(i18n("launcher.april_fools.switch_lzh"), null, 10,
|
||||
MessageType.QUESTION, () -> {
|
||||
Controllers.confirm(i18n("launcher.april_fools.switch_lzh.confirm"), null, MessageType.QUESTION, () -> {
|
||||
LOG.info("Switching locale to " + lzh);
|
||||
|
||||
updateShowTips.run();
|
||||
config().setLocalization(lzh);
|
||||
|
||||
Controllers.onApplicationStop();
|
||||
|
||||
try {
|
||||
FileSaver.waitForAllSaves();
|
||||
} catch (InterruptedException ignored) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
try {
|
||||
Restarter.restartSelf();
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to restart self", e);
|
||||
}
|
||||
|
||||
Platform.exit();
|
||||
}, updateShowTips);
|
||||
}, updateShowTips);
|
||||
}
|
||||
}
|
||||
|
||||
public static void dialog(Region content) {
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.ui;
|
||||
import com.jfoenix.controls.*;
|
||||
import javafx.animation.Animation;
|
||||
import javafx.animation.Interpolator;
|
||||
import javafx.animation.PauseTransition;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
@@ -37,7 +38,6 @@ import javafx.event.EventDispatcher;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.event.EventType;
|
||||
import javafx.geometry.Bounds;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.geometry.Rectangle2D;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
@@ -51,7 +51,6 @@ import javafx.scene.input.*;
|
||||
import javafx.scene.layout.ColumnConstraints;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
@@ -68,6 +67,7 @@ import org.jackhuang.hmcl.task.CacheFileTask;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
import org.jackhuang.hmcl.ui.animation.Motion;
|
||||
import org.jackhuang.hmcl.ui.construct.IconedMenuItem;
|
||||
import org.jackhuang.hmcl.ui.construct.MenuSeparator;
|
||||
import org.jackhuang.hmcl.ui.construct.PopupMenu;
|
||||
@@ -417,14 +417,6 @@ public final class FXUtils {
|
||||
return region.getMaxHeight();
|
||||
}
|
||||
|
||||
public static Node limitingSize(Node node, double width, double height) {
|
||||
StackPane pane = new StackPane(node);
|
||||
pane.setAlignment(Pos.CENTER);
|
||||
FXUtils.setLimitWidth(pane, width);
|
||||
FXUtils.setLimitHeight(pane, height);
|
||||
return pane;
|
||||
}
|
||||
|
||||
public static void limitCellWidth(ListView<?> listView, ListCell<?> cell) {
|
||||
ReadOnlyDoubleProperty widthProperty;
|
||||
|
||||
@@ -1249,8 +1241,8 @@ public final class FXUtils {
|
||||
.setSignificance(Task.TaskSignificance.MINOR);
|
||||
}
|
||||
|
||||
public static Task<Image> getRemoteImageTask(URI uri, int requestedWidth, int requestedHeight, boolean preserveRatio, boolean smooth) {
|
||||
return new CacheFileTask(uri)
|
||||
public static Task<Image> getRemoteImageTask(List<URI> uris, int requestedWidth, int requestedHeight, boolean preserveRatio, boolean smooth) {
|
||||
return new CacheFileTask(uris)
|
||||
.setSignificance(Task.TaskSignificance.MINOR)
|
||||
.thenApplyAsync(file -> loadImage(file, requestedWidth, requestedHeight, preserveRatio, smooth))
|
||||
.setSignificance(Task.TaskSignificance.MINOR);
|
||||
@@ -1291,6 +1283,30 @@ public final class FXUtils {
|
||||
return button;
|
||||
}
|
||||
|
||||
public static JFXButton newToggleButton4(SVG icon, int size) {
|
||||
JFXButton button = new JFXButton();
|
||||
button.getStyleClass().add("toggle-icon4");
|
||||
button.setGraphic(icon.createIcon(size));
|
||||
return button;
|
||||
}
|
||||
|
||||
public static void setOnActionWithCooldown(ButtonBase button, Runnable action) {
|
||||
setOnActionWithCooldown(button, action, Motion.SHORT4);
|
||||
}
|
||||
|
||||
public static void setOnActionWithCooldown(ButtonBase button, Runnable action, Duration cooldown) {
|
||||
button.setOnAction(e -> {
|
||||
button.setDisable(true);
|
||||
|
||||
var pause = new PauseTransition(cooldown);
|
||||
pause.setOnFinished(event -> button.setDisable(false));
|
||||
pause.play();
|
||||
|
||||
action.run();
|
||||
e.consume();
|
||||
});
|
||||
}
|
||||
|
||||
public static Label newSafeTruncatedLabel() {
|
||||
Label label = new Label();
|
||||
label.setTextOverrun(OverrunStyle.CENTER_WORD_ELLIPSIS);
|
||||
|
||||
@@ -28,16 +28,15 @@ import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.*;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.setting.VersionIconType;
|
||||
import org.jackhuang.hmcl.ui.construct.ImageContainer;
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
@@ -295,14 +294,14 @@ public class InstallerItem extends Control {
|
||||
paneWrapper.pseudoClassStateChanged(CARD, control.style == Style.CARD);
|
||||
|
||||
if (control.iconType != null) {
|
||||
ImageView view = new ImageView(control.iconType.getIcon());
|
||||
Node node = FXUtils.limitingSize(view, 32, 32);
|
||||
node.setMouseTransparent(true);
|
||||
node.getStyleClass().add("installer-item-image");
|
||||
pane.getChildren().add(node);
|
||||
var imageContainer = new ImageContainer(32);
|
||||
imageContainer.setImage(control.iconType.getIcon());
|
||||
imageContainer.setMouseTransparent(true);
|
||||
imageContainer.getStyleClass().add("installer-item-image");
|
||||
pane.getChildren().add(imageContainer);
|
||||
|
||||
if (control.style == Style.CARD) {
|
||||
VBox.setMargin(node, new Insets(8, 0, 16, 0));
|
||||
VBox.setMargin(imageContainer, new Insets(8, 0, 16, 0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,9 +347,7 @@ public class InstallerItem extends Control {
|
||||
buttonsContainer.setAlignment(Pos.CENTER);
|
||||
pane.getChildren().add(buttonsContainer);
|
||||
|
||||
JFXButton removeButton = new JFXButton();
|
||||
removeButton.setGraphic(SVG.CLOSE.createIcon());
|
||||
removeButton.getStyleClass().add("toggle-icon4");
|
||||
JFXButton removeButton = FXUtils.newToggleButton4(SVG.CLOSE);
|
||||
if (control.id.equals(MINECRAFT.getPatchId())) {
|
||||
removeButton.setVisible(false);
|
||||
} else {
|
||||
|
||||
@@ -411,11 +411,12 @@ public final class LogWindow extends Stage {
|
||||
terminateButton.setOnAction(e -> getSkinnable().onTerminateGame());
|
||||
|
||||
SpinnerPane exportDumpPane = new SpinnerPane();
|
||||
exportDumpPane.getStyleClass().add("small-spinner-pane");
|
||||
JFXButton exportDumpButton = new JFXButton(i18n("logwindow.export_dump"));
|
||||
if (SystemUtils.supportJVMAttachment()) {
|
||||
exportDumpButton.setOnAction(e -> getSkinnable().onExportDump(exportDumpPane));
|
||||
} else {
|
||||
exportDumpButton.setTooltip(new Tooltip(i18n("logwindow.export_dump.no_dependency")));
|
||||
FXUtils.installFastTooltip(exportDumpPane, i18n("logwindow.export_dump.no_dependency"));
|
||||
exportDumpButton.setDisable(true);
|
||||
}
|
||||
exportDumpPane.setContent(exportDumpButton);
|
||||
|
||||
@@ -125,7 +125,7 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
spinnerMove.setContent(btnMove);
|
||||
right.getChildren().add(spinnerMove);
|
||||
|
||||
JFXButton btnRefresh = new JFXButton();
|
||||
JFXButton btnRefresh = FXUtils.newToggleButton4(SVG.REFRESH);
|
||||
SpinnerPane spinnerRefresh = new SpinnerPane();
|
||||
spinnerRefresh.getStyleClass().setAll("small-spinner-pane");
|
||||
if (skinnable.getAccount() instanceof MicrosoftAccount && Accounts.OAUTH_CALLBACK.getClientId().isEmpty()) {
|
||||
@@ -144,13 +144,11 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
})
|
||||
.start();
|
||||
});
|
||||
btnRefresh.getStyleClass().add("toggle-icon4");
|
||||
btnRefresh.setGraphic(SVG.REFRESH.createIcon());
|
||||
FXUtils.installFastTooltip(btnRefresh, i18n("button.refresh"));
|
||||
spinnerRefresh.setContent(btnRefresh);
|
||||
right.getChildren().add(spinnerRefresh);
|
||||
|
||||
JFXButton btnUpload = new JFXButton();
|
||||
JFXButton btnUpload = FXUtils.newToggleButton4(SVG.CHECKROOM);
|
||||
SpinnerPane spinnerUpload = new SpinnerPane();
|
||||
btnUpload.setOnAction(e -> {
|
||||
Task<?> uploadTask = skinnable.uploadSkin();
|
||||
@@ -161,29 +159,23 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
.start();
|
||||
}
|
||||
});
|
||||
btnUpload.getStyleClass().add("toggle-icon4");
|
||||
btnUpload.setGraphic(SVG.CHECKROOM.createIcon());
|
||||
FXUtils.installFastTooltip(btnUpload, i18n("account.skin.upload"));
|
||||
btnUpload.disableProperty().bind(Bindings.not(skinnable.canUploadSkin()));
|
||||
spinnerUpload.setContent(btnUpload);
|
||||
spinnerUpload.getStyleClass().add("small-spinner-pane");
|
||||
right.getChildren().add(spinnerUpload);
|
||||
|
||||
JFXButton btnCopyUUID = new JFXButton();
|
||||
JFXButton btnCopyUUID = FXUtils.newToggleButton4(SVG.CONTENT_COPY);
|
||||
SpinnerPane spinnerCopyUUID = new SpinnerPane();
|
||||
spinnerCopyUUID.getStyleClass().add("small-spinner-pane");
|
||||
btnUpload.getStyleClass().add("toggle-icon4");
|
||||
btnCopyUUID.setOnAction(e -> FXUtils.copyText(skinnable.getAccount().getUUID().toString()));
|
||||
btnCopyUUID.setGraphic(SVG.CONTENT_COPY.createIcon());
|
||||
FXUtils.installFastTooltip(btnCopyUUID, i18n("account.copy_uuid"));
|
||||
spinnerCopyUUID.setContent(btnCopyUUID);
|
||||
right.getChildren().add(spinnerCopyUUID);
|
||||
|
||||
JFXButton btnRemove = new JFXButton();
|
||||
JFXButton btnRemove = FXUtils.newToggleButton4(SVG.DELETE_FOREVER);
|
||||
btnRemove.setOnAction(e -> Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), skinnable::remove, null));
|
||||
btnRemove.getStyleClass().add("toggle-icon4");
|
||||
BorderPane.setAlignment(btnRemove, Pos.CENTER);
|
||||
btnRemove.setGraphic(SVG.DELETE.createIcon());
|
||||
FXUtils.installFastTooltip(btnRemove, i18n("button.delete"));
|
||||
right.getChildren().add(btnRemove);
|
||||
root.setRight(right);
|
||||
|
||||
@@ -399,9 +399,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
});
|
||||
linksContainer.setMinWidth(USE_PREF_SIZE);
|
||||
|
||||
JFXButton btnAddServer = new JFXButton();
|
||||
btnAddServer.setGraphic(SVG.ADD.createIcon(20));
|
||||
btnAddServer.getStyleClass().add("toggle-icon4");
|
||||
JFXButton btnAddServer = FXUtils.newToggleButton4(SVG.ADD, 20);
|
||||
btnAddServer.setOnAction(e -> {
|
||||
Controllers.dialog(new AddAuthlibInjectorServerPane());
|
||||
});
|
||||
@@ -566,7 +564,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
}
|
||||
}
|
||||
|
||||
private static class DialogCharacterSelector extends BorderPane implements CharacterSelector {
|
||||
public static class DialogCharacterSelector extends JFXDialogLayout implements CharacterSelector {
|
||||
|
||||
private final AdvancedListBox listBox = new AdvancedListBox();
|
||||
private final JFXButton cancel = new JFXButton();
|
||||
@@ -578,17 +576,17 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
setStyle("-fx-padding: 8px;");
|
||||
|
||||
cancel.setText(i18n("button.cancel"));
|
||||
StackPane.setAlignment(cancel, Pos.BOTTOM_RIGHT);
|
||||
cancel.setOnAction(e -> latch.countDown());
|
||||
cancel.getStyleClass().add("dialog-cancel");
|
||||
|
||||
listBox.startCategory(i18n("account.choose").toUpperCase(Locale.ROOT));
|
||||
|
||||
setCenter(listBox);
|
||||
setBody(listBox);
|
||||
|
||||
HBox hbox = new HBox();
|
||||
hbox.setAlignment(Pos.CENTER_RIGHT);
|
||||
hbox.getChildren().add(cancel);
|
||||
setBottom(hbox);
|
||||
setActions(hbox);
|
||||
|
||||
onEscPressed(this, cancel::fire);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.account;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
@@ -175,16 +192,20 @@ public class MicrosoftAccountLoginPane extends JFXDialogLayout implements Dialog
|
||||
} else if (currentStep instanceof Step.WaitForScanQrCode wait) {
|
||||
loginButtonSpinner.setLoading(true);
|
||||
|
||||
String scanUri = "https://www.microsoft.com/link".equals(wait.verificationUri())
|
||||
? "https://www.microsoft.com/link?otc=" + wait.userCode()
|
||||
: wait.verificationUri();
|
||||
|
||||
var deviceHint = new HintPane(MessageDialogPane.MessageType.INFO);
|
||||
deviceHint.setSegment(i18n("account.methods.microsoft.methods.device.hint",
|
||||
StringUtils.escapeXmlAttribute(wait.verificationUri()),
|
||||
StringUtils.escapeXmlAttribute(scanUri),
|
||||
wait.verificationUri(),
|
||||
wait.userCode()
|
||||
));
|
||||
|
||||
var qrCode = new SVGPath();
|
||||
qrCode.fillProperty().bind(Themes.colorSchemeProperty().getPrimary());
|
||||
qrCode.setContent(QrCodeUtils.toSVGPath(QrCode.encodeText(wait.verificationUri() + "?otc=" + wait.userCode(), QrCode.Ecc.MEDIUM)));
|
||||
qrCode.setContent(QrCodeUtils.toSVGPath(QrCode.encodeText(scanUri, QrCode.Ecc.MEDIUM)));
|
||||
qrCode.setScaleX(3);
|
||||
qrCode.setScaleY(3);
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
@@ -85,13 +84,11 @@ public class AdvancedListItem extends Control {
|
||||
}
|
||||
|
||||
public void setRightAction(SVG icon, Runnable action) {
|
||||
var button = new JFXButton();
|
||||
var button = FXUtils.newToggleButton4(icon, 14);
|
||||
button.setOnAction(e -> {
|
||||
action.run();
|
||||
e.consume();
|
||||
});
|
||||
button.getStyleClass().add("toggle-icon4");
|
||||
button.setGraphic(icon.createIcon(14));
|
||||
setAlignment(button, Pos.CENTER);
|
||||
setRightGraphic(button);
|
||||
}
|
||||
|
||||
@@ -80,8 +80,7 @@ public class FileSelector extends HBox {
|
||||
JFXTextField customField = new JFXTextField();
|
||||
FXUtils.bindString(customField, valueProperty());
|
||||
|
||||
JFXButton selectButton = new JFXButton();
|
||||
selectButton.setGraphic(SVG.FOLDER_OPEN.createIcon(15));
|
||||
JFXButton selectButton = FXUtils.newToggleButton4(SVG.FOLDER_OPEN, 15);
|
||||
selectButton.setOnAction(e -> {
|
||||
if (directory) {
|
||||
DirectoryChooser chooser = new DirectoryChooser();
|
||||
|
||||
@@ -129,12 +129,12 @@ public class FloatScrollBarSkin implements Skin<ScrollBar> {
|
||||
track.heightProperty().unbind();
|
||||
|
||||
if (scrollBar.getOrientation() == Orientation.HORIZONTAL) {
|
||||
track.relocate(0, -5);
|
||||
track.relocate(0, -8);
|
||||
track.widthProperty().bind(scrollBar.widthProperty());
|
||||
track.setHeight(5);
|
||||
track.setHeight(8);
|
||||
} else {
|
||||
track.relocate(-5, 0);
|
||||
track.setWidth(5);
|
||||
track.relocate(-8, 0);
|
||||
track.setWidth(8);
|
||||
track.heightProperty().bind(scrollBar.heightProperty());
|
||||
}
|
||||
|
||||
@@ -144,13 +144,13 @@ public class FloatScrollBarSkin implements Skin<ScrollBar> {
|
||||
thumb.heightProperty().unbind();
|
||||
|
||||
if (scrollBar.getOrientation() == Orientation.HORIZONTAL) {
|
||||
thumb.relocate(0, -5);
|
||||
thumb.relocate(0, -8);
|
||||
thumb.widthProperty().bind(Bindings.max(20, scrollBar.visibleAmountProperty().divide(range).multiply(scrollBar.widthProperty())));
|
||||
thumb.setHeight(5);
|
||||
thumb.setHeight(8);
|
||||
thumb.xProperty().bind(Bindings.subtract(scrollBar.widthProperty(), thumb.widthProperty()).multiply(position));
|
||||
} else {
|
||||
thumb.relocate(-5, 0);
|
||||
thumb.setWidth(5);
|
||||
thumb.relocate(-8, 0);
|
||||
thumb.setWidth(8);
|
||||
thumb.heightProperty().bind(Bindings.max(20, scrollBar.visibleAmountProperty().divide(range).multiply(scrollBar.heightProperty())));
|
||||
thumb.yProperty().bind(Bindings.subtract(scrollBar.heightProperty(), thumb.heightProperty()).multiply(position));
|
||||
}
|
||||
@@ -162,7 +162,7 @@ public class FloatScrollBarSkin implements Skin<ScrollBar> {
|
||||
return Double.MAX_VALUE;
|
||||
}
|
||||
|
||||
return 5;
|
||||
return 8;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -171,7 +171,7 @@ public class FloatScrollBarSkin implements Skin<ScrollBar> {
|
||||
return Double.MAX_VALUE;
|
||||
}
|
||||
|
||||
return 5;
|
||||
return 8;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.css.CssMetaData;
|
||||
import javafx.css.Styleable;
|
||||
import javafx.css.StyleableDoubleProperty;
|
||||
import javafx.css.StyleableProperty;
|
||||
import javafx.css.converter.SizeConverter;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/// A custom ImageView with fixed size and corner radius support.
|
||||
public class ImageContainer extends StackPane {
|
||||
|
||||
private static final String DEFAULT_STYLE_CLASS = "image-container";
|
||||
|
||||
private final ImageView imageView = new ImageView();
|
||||
private final Rectangle clip = new Rectangle();
|
||||
|
||||
public ImageContainer(double size) {
|
||||
this(size, size);
|
||||
}
|
||||
|
||||
public ImageContainer(double width, double height) {
|
||||
this.getStyleClass().add(DEFAULT_STYLE_CLASS);
|
||||
|
||||
FXUtils.setLimitWidth(this, width);
|
||||
FXUtils.setLimitHeight(this, height);
|
||||
|
||||
imageView.setPreserveRatio(true);
|
||||
FXUtils.limitSize(imageView, width, height);
|
||||
StackPane.setAlignment(imageView, Pos.CENTER);
|
||||
|
||||
clip.setWidth(width);
|
||||
clip.setHeight(height);
|
||||
updateCornerRadius(getCornerRadius());
|
||||
this.setClip(clip);
|
||||
|
||||
this.getChildren().setAll(imageView);
|
||||
}
|
||||
|
||||
private void updateCornerRadius(double radius) {
|
||||
clip.setArcWidth(radius);
|
||||
clip.setArcHeight(radius);
|
||||
}
|
||||
|
||||
private static final double DEFAULT_CORNER_RADIUS = 6.0;
|
||||
|
||||
private StyleableDoubleProperty cornerRadius;
|
||||
|
||||
public StyleableDoubleProperty cornerRadiusProperty() {
|
||||
if (this.cornerRadius == null) {
|
||||
cornerRadius = new StyleableDoubleProperty() {
|
||||
@Override
|
||||
public Object getBean() {
|
||||
return ImageContainer.this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "cornerRadius";
|
||||
}
|
||||
|
||||
@Override
|
||||
public CssMetaData<? extends Styleable, Number> getCssMetaData() {
|
||||
return StyleableProperties.CORNER_RADIUS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
updateCornerRadius(get());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return cornerRadius;
|
||||
}
|
||||
|
||||
public double getCornerRadius() {
|
||||
return cornerRadius == null ? DEFAULT_CORNER_RADIUS : cornerRadius.get();
|
||||
}
|
||||
|
||||
public void setCornerRadius(double radius) {
|
||||
cornerRadiusProperty().set(radius);
|
||||
}
|
||||
|
||||
public ObjectProperty<Image> imageProperty() {
|
||||
return imageView.imageProperty();
|
||||
}
|
||||
|
||||
public Image getImage() {
|
||||
return imageView.getImage();
|
||||
}
|
||||
|
||||
public void setImage(Image image) {
|
||||
imageView.setImage(image);
|
||||
}
|
||||
|
||||
public BooleanProperty smoothProperty() {
|
||||
return imageView.smoothProperty();
|
||||
}
|
||||
|
||||
public boolean isSmooth() {
|
||||
return imageView.isSmooth();
|
||||
}
|
||||
|
||||
public void setSmooth(boolean smooth) {
|
||||
imageView.setSmooth(smooth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
|
||||
return StyleableProperties.STYLEABLES;
|
||||
}
|
||||
|
||||
private static final class StyleableProperties {
|
||||
private static final CssMetaData<ImageContainer, Number> CORNER_RADIUS =
|
||||
new CssMetaData<>("-jfx-corner-radius", SizeConverter.getInstance(), DEFAULT_CORNER_RADIUS) {
|
||||
@Override
|
||||
public boolean isSettable(ImageContainer control) {
|
||||
return control.cornerRadius == null || !control.cornerRadius.isBound();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StyleableProperty<Number> getStyleableProperty(ImageContainer control) {
|
||||
return control.cornerRadiusProperty();
|
||||
}
|
||||
};
|
||||
|
||||
private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
|
||||
|
||||
static {
|
||||
var styleables = new ArrayList<>(StackPane.getClassCssMetaData());
|
||||
styleables.add(CORNER_RADIUS);
|
||||
STYLEABLES = List.copyOf(styleables);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,6 @@ import javafx.event.EventHandler;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
@@ -40,7 +39,7 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
@DefaultProperty("image")
|
||||
public final class ImagePickerItem extends BorderPane {
|
||||
|
||||
private final ImageView imageView;
|
||||
private final ImageContainer imageContainer;
|
||||
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title");
|
||||
private final ObjectProperty<EventHandler<ActionEvent>> onSelectButtonClicked = new SimpleObjectProperty<>(this, "onSelectButtonClicked");
|
||||
@@ -48,25 +47,20 @@ public final class ImagePickerItem extends BorderPane {
|
||||
private final ObjectProperty<Image> image = new SimpleObjectProperty<>(this, "image");
|
||||
|
||||
public ImagePickerItem() {
|
||||
imageView = new ImageView();
|
||||
imageView.setSmooth(false);
|
||||
imageView.setPreserveRatio(true);
|
||||
imageContainer = new ImageContainer(32);
|
||||
imageContainer.setSmooth(false);
|
||||
|
||||
JFXButton selectButton = new JFXButton();
|
||||
selectButton.setGraphic(SVG.EDIT.createIcon(20));
|
||||
JFXButton selectButton = FXUtils.newToggleButton4(SVG.EDIT, 20);
|
||||
selectButton.onActionProperty().bind(onSelectButtonClicked);
|
||||
selectButton.getStyleClass().add("toggle-icon4");
|
||||
|
||||
JFXButton deleteButton = new JFXButton();
|
||||
deleteButton.setGraphic(SVG.RESTORE.createIcon(20));
|
||||
JFXButton deleteButton = FXUtils.newToggleButton4(SVG.RESTORE, 20);
|
||||
deleteButton.onActionProperty().bind(onDeleteButtonClicked);
|
||||
deleteButton.getStyleClass().add("toggle-icon4");
|
||||
|
||||
FXUtils.installFastTooltip(selectButton, i18n("button.edit"));
|
||||
FXUtils.installFastTooltip(deleteButton, i18n("button.reset"));
|
||||
|
||||
HBox hBox = new HBox();
|
||||
hBox.getChildren().setAll(imageView, selectButton, deleteButton);
|
||||
hBox.getChildren().setAll(imageContainer, selectButton, deleteButton);
|
||||
hBox.setAlignment(Pos.CENTER_RIGHT);
|
||||
hBox.setSpacing(8);
|
||||
setRight(hBox);
|
||||
@@ -78,7 +72,7 @@ public final class ImagePickerItem extends BorderPane {
|
||||
vBox.setAlignment(Pos.CENTER_LEFT);
|
||||
setLeft(vBox);
|
||||
|
||||
imageView.imageProperty().bind(image);
|
||||
imageContainer.imageProperty().bind(image);
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
@@ -128,8 +122,4 @@ public final class ImagePickerItem extends BorderPane {
|
||||
public void setImage(Image image) {
|
||||
this.image.set(image);
|
||||
}
|
||||
|
||||
public ImageView getImageView() {
|
||||
return imageView;
|
||||
}
|
||||
}
|
||||
@@ -20,16 +20,18 @@ package org.jackhuang.hmcl.ui.construct;
|
||||
import com.jfoenix.controls.JFXPopup;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.beans.property.ListProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleListProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.input.ScrollEvent;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.input.*;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||
@@ -81,6 +83,8 @@ public final class LineSelectButton<T> extends LineButton {
|
||||
PopupMenu popupMenu = new PopupMenu();
|
||||
this.popup = new JFXPopup(popupMenu);
|
||||
|
||||
popupMenu.addEventHandler(KeyEvent.KEY_PRESSED, this::handleKeyEvent);
|
||||
|
||||
ripplerContainer.addEventFilter(ScrollEvent.ANY, ignored -> popup.hide());
|
||||
|
||||
Bindings.bindContent(popupMenu.getContent(), MappedObservableList.create(itemsProperty(), item -> {
|
||||
@@ -213,4 +217,32 @@ public final class LineSelectButton<T> extends LineButton {
|
||||
return items.get();
|
||||
}
|
||||
|
||||
private void handleKeyEvent(KeyEvent event) {
|
||||
ObservableList<T> list = getItems();
|
||||
if (list == null || list.isEmpty()) return;
|
||||
int index = list.indexOf(getValue());
|
||||
|
||||
var code = event.getCode();
|
||||
|
||||
if (code == KeyCode.UP) {
|
||||
if (index > 0) {
|
||||
setValue(list.get(index - 1));
|
||||
} else {
|
||||
setValue(list.get(list.size() - 1));
|
||||
}
|
||||
event.consume();
|
||||
} else if (code == KeyCode.DOWN) {
|
||||
if (index < list.size() - 1) {
|
||||
setValue(list.get(index + 1));
|
||||
} else {
|
||||
setValue(list.get(0));
|
||||
}
|
||||
event.consume();
|
||||
} else if (code == KeyCode.ENTER || code == KeyCode.ESCAPE) {
|
||||
if (popup != null && popup.isShowing()) {
|
||||
popup.hide();
|
||||
event.consume();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -189,6 +189,7 @@ public class SpinnerPane extends Control {
|
||||
|
||||
Skin(SpinnerPane control) {
|
||||
super(control);
|
||||
root.setClip(null);
|
||||
|
||||
updateContent();
|
||||
this.getChildren().setAll(root);
|
||||
|
||||
@@ -83,8 +83,8 @@ import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class TaskListPane extends StackPane {
|
||||
private static final Insets DEFAULT_PROGRESS_NODE_PADDING = new Insets(0, 0, 8, 0);
|
||||
private static final Insets STAGED_PROGRESS_NODE_PADDING = new Insets(0, 0, 8, 26);
|
||||
private static final Insets DEFAULT_PROGRESS_NODE_PADDING = new Insets(0, 0, 4, 0);
|
||||
private static final Insets STAGED_PROGRESS_NODE_PADDING = new Insets(0, 0, 4, 26);
|
||||
|
||||
private TaskExecutor executor;
|
||||
private final JFXListView<Node> listView = new JFXListView<>();
|
||||
@@ -279,6 +279,7 @@ public final class TaskListPane extends StackPane {
|
||||
|
||||
private final class Cell extends ListCell<Node> {
|
||||
private static final double STATUS_ICON_SIZE = 14;
|
||||
private static final Insets PROGRESS_BAR_MARGIN = new Insets(2, 0, 0, 0);
|
||||
|
||||
private final BorderPane pane = new BorderPane();
|
||||
private final StackPane left = new StackPane();
|
||||
@@ -310,6 +311,7 @@ public final class TaskListPane extends StackPane {
|
||||
bar.minWidthProperty().bind(barWidth);
|
||||
bar.prefWidthProperty().bind(barWidth);
|
||||
bar.maxWidthProperty().bind(barWidth);
|
||||
BorderPane.setMargin(bar, PROGRESS_BAR_MARGIN);
|
||||
|
||||
setGraphic(pane);
|
||||
}
|
||||
@@ -325,8 +327,9 @@ public final class TaskListPane extends StackPane {
|
||||
pane.paddingProperty().unbind();
|
||||
title.textProperty().unbind();
|
||||
message.textProperty().unbind();
|
||||
bar.progressProperty().unbind();
|
||||
|
||||
bar.setSmoothProgress(false);
|
||||
bar.progressProperty().unbind();
|
||||
StageNode prevStageNode;
|
||||
if (prevStageNodeRef != null && (prevStageNode = prevStageNodeRef.get()) != null)
|
||||
prevStageNode.status.removeListener(statusChangeListener);
|
||||
@@ -364,6 +367,8 @@ public final class TaskListPane extends StackPane {
|
||||
pane.setRight(null);
|
||||
pane.setBottom(null);
|
||||
}
|
||||
|
||||
bar.setSmoothProgress(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ import javafx.scene.paint.Color;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
import org.jackhuang.hmcl.ui.animation.Motion;
|
||||
@@ -46,13 +47,10 @@ import org.jackhuang.hmcl.ui.wizard.Navigation;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
public class Decorator extends Control {
|
||||
private final ListProperty<Node> drawer = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
private final ListProperty<Node> content = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
private final ListProperty<Node> container = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
private final ObjectProperty<Background> contentBackground = new SimpleObjectProperty<>();
|
||||
private final ObjectProperty<DecoratorPage.State> state = new SimpleObjectProperty<>();
|
||||
private final StringProperty drawerTitle = new SimpleStringProperty();
|
||||
private final ObjectProperty<Runnable> onCloseButtonAction = new SimpleObjectProperty<>();
|
||||
private final ObjectProperty<EventHandler<ActionEvent>> onCloseNavButtonAction = new SimpleObjectProperty<>();
|
||||
private final ObjectProperty<EventHandler<ActionEvent>> onBackNavButtonAction = new SimpleObjectProperty<>();
|
||||
private final ObjectProperty<EventHandler<ActionEvent>> onRefreshNavButtonAction = new SimpleObjectProperty<>();
|
||||
@@ -117,18 +115,6 @@ public class Decorator extends Control {
|
||||
this.drawerWrapper = drawerWrapper;
|
||||
}
|
||||
|
||||
public ObservableList<Node> getDrawer() {
|
||||
return drawer.get();
|
||||
}
|
||||
|
||||
public ListProperty<Node> drawerProperty() {
|
||||
return drawer;
|
||||
}
|
||||
|
||||
public void setDrawer(ObservableList<Node> drawer) {
|
||||
this.drawer.set(drawer);
|
||||
}
|
||||
|
||||
public ObservableList<Node> getContent() {
|
||||
return content.get();
|
||||
}
|
||||
@@ -153,30 +139,6 @@ public class Decorator extends Control {
|
||||
this.state.set(state);
|
||||
}
|
||||
|
||||
public String getDrawerTitle() {
|
||||
return drawerTitle.get();
|
||||
}
|
||||
|
||||
public StringProperty drawerTitleProperty() {
|
||||
return drawerTitle;
|
||||
}
|
||||
|
||||
public void setDrawerTitle(String drawerTitle) {
|
||||
this.drawerTitle.set(drawerTitle);
|
||||
}
|
||||
|
||||
public Runnable getOnCloseButtonAction() {
|
||||
return onCloseButtonAction.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Runnable> onCloseButtonActionProperty() {
|
||||
return onCloseButtonAction;
|
||||
}
|
||||
|
||||
public void setOnCloseButtonAction(Runnable onCloseButtonAction) {
|
||||
this.onCloseButtonAction.set(onCloseButtonAction);
|
||||
}
|
||||
|
||||
public ObservableList<Node> getContainer() {
|
||||
return container.get();
|
||||
}
|
||||
@@ -301,7 +263,26 @@ public class Decorator extends Control {
|
||||
}
|
||||
|
||||
public void close() {
|
||||
onCloseButtonAction.get().run();
|
||||
if (AnimationUtils.playWindowAnimation()) {
|
||||
Timeline timeline = new Timeline(
|
||||
new KeyFrame(Duration.millis(0),
|
||||
new KeyValue(opacityProperty(), 1, Motion.EASE),
|
||||
new KeyValue(scaleXProperty(), 1, Motion.EASE),
|
||||
new KeyValue(scaleYProperty(), 1, Motion.EASE),
|
||||
new KeyValue(scaleZProperty(), 0.3, Motion.EASE)
|
||||
),
|
||||
new KeyFrame(Duration.millis(200),
|
||||
new KeyValue(opacityProperty(), 0, Motion.EASE),
|
||||
new KeyValue(scaleXProperty(), 0.8, Motion.EASE),
|
||||
new KeyValue(scaleYProperty(), 0.8, Motion.EASE),
|
||||
new KeyValue(scaleZProperty(), 0.8, Motion.EASE)
|
||||
)
|
||||
);
|
||||
timeline.setOnFinished(event -> Launcher.stopApplication());
|
||||
timeline.play();
|
||||
} else {
|
||||
Launcher.stopApplication();
|
||||
}
|
||||
}
|
||||
|
||||
public void capableDraggingWindow(Node node) {
|
||||
|
||||
@@ -20,9 +20,6 @@ package org.jackhuang.hmcl.ui.decorator;
|
||||
import com.jfoenix.controls.JFXSnackbar;
|
||||
import com.jfoenix.controls.JFXSnackbarLayout;
|
||||
import javafx.animation.Interpolator;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.WeakInvalidationListener;
|
||||
import javafx.geometry.Insets;
|
||||
@@ -37,7 +34,6 @@ import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDnD;
|
||||
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
|
||||
@@ -47,7 +43,6 @@ import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.DialogUtils;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.account.AddAuthlibInjectorServerPane;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.Motion;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane.AnimationProducer;
|
||||
@@ -62,13 +57,12 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
import java.util.Random;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.newBuiltinImage;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
|
||||
@@ -81,28 +75,6 @@ public class DecoratorController {
|
||||
|
||||
public DecoratorController(Stage stage, Node mainPage) {
|
||||
decorator = new Decorator(stage);
|
||||
decorator.setOnCloseButtonAction(() -> {
|
||||
if (AnimationUtils.playWindowAnimation()) {
|
||||
Timeline timeline = new Timeline(
|
||||
new KeyFrame(Duration.millis(0),
|
||||
new KeyValue(decorator.opacityProperty(), 1, Motion.EASE),
|
||||
new KeyValue(decorator.scaleXProperty(), 1, Motion.EASE),
|
||||
new KeyValue(decorator.scaleYProperty(), 1, Motion.EASE),
|
||||
new KeyValue(decorator.scaleZProperty(), 0.3, Motion.EASE)
|
||||
),
|
||||
new KeyFrame(Duration.millis(200),
|
||||
new KeyValue(decorator.opacityProperty(), 0, Motion.EASE),
|
||||
new KeyValue(decorator.scaleXProperty(), 0.8, Motion.EASE),
|
||||
new KeyValue(decorator.scaleYProperty(), 0.8, Motion.EASE),
|
||||
new KeyValue(decorator.scaleZProperty(), 0.8, Motion.EASE)
|
||||
)
|
||||
);
|
||||
timeline.setOnFinished(event -> Launcher.stopApplication());
|
||||
timeline.play();
|
||||
} else {
|
||||
Launcher.stopApplication();
|
||||
}
|
||||
});
|
||||
decorator.titleTransparentProperty().bind(config().titleTransparentProperty());
|
||||
|
||||
navigator = new Navigator();
|
||||
@@ -221,7 +193,10 @@ public class DecoratorController {
|
||||
String backgroundImage = config().getBackgroundImage();
|
||||
if (backgroundImage != null)
|
||||
try {
|
||||
image = tryLoadImage(Paths.get(backgroundImage));
|
||||
Path path = Path.of(backgroundImage);
|
||||
image = Files.isDirectory(path)
|
||||
? randomImageIn(path)
|
||||
: tryLoadImage(path);
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Couldn't load background image", e);
|
||||
}
|
||||
@@ -327,14 +302,14 @@ public class DecoratorController {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Path> candidates;
|
||||
ArrayList<Path> candidates;
|
||||
try (Stream<Path> stream = Files.list(imageDir)) {
|
||||
candidates = stream
|
||||
.filter(it -> FXUtils.IMAGE_EXTENSIONS.contains(getExtension(it).toLowerCase(Locale.ROOT)))
|
||||
.filter(Files::isReadable)
|
||||
.collect(toList());
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to list files in ./bg", e);
|
||||
LOG.warning("Failed to list files in " + imageDir, e);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -84,8 +84,8 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
newGameTab.setNodeSupplier(loadVersionFor(() -> new VersionsPage(versionPageNavigator, i18n("install.installer.choose", i18n("install.installer.game")), "", DownloadProviders.getDownloadProvider(),
|
||||
"game", versionPageNavigator::onGameSelected)));
|
||||
modpackTab.setNodeSupplier(loadVersionFor(() -> {
|
||||
DownloadListPage page = HMCLLocalizedDownloadListPage.ofModPack((profile, __, mod, file) -> {
|
||||
Versions.downloadModpackImpl(profile, uploadVersion, mod, file);
|
||||
DownloadListPage page = HMCLLocalizedDownloadListPage.ofModPack((downloadProvider, profile, __, mod, file) -> {
|
||||
Versions.downloadModpackImpl(downloadProvider, profile, uploadVersion, mod, file);
|
||||
}, false);
|
||||
|
||||
JFXButton installLocalModpackButton = FXUtils.newRaisedButton(i18n("install.modpack"));
|
||||
@@ -94,9 +94,9 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
page.getActions().add(installLocalModpackButton);
|
||||
return page;
|
||||
}));
|
||||
modTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofMod((profile, version, mod, file) -> download(profile, version, file, "mods"), true)));
|
||||
resourcePackTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofResourcePack((profile, version, mod, file) -> download(profile, version, file, "resourcepacks"), true)));
|
||||
shaderTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofShaderPack((profile, version, mod, file) -> download(profile, version, file, "shaderpacks"), true)));
|
||||
modTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofMod((downloadProvider, profile, version, mod, file) -> download(downloadProvider, profile, version, file, "mods"), true)));
|
||||
resourcePackTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofResourcePack((downloadProvider, profile, version, mod, file) -> download(downloadProvider, profile, version, file, "resourcepacks"), true)));
|
||||
shaderTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofShaderPack((downloadProvider, profile, version, mod, file) -> download(downloadProvider, profile, version, file, "shaderpacks"), true)));
|
||||
worldTab.setNodeSupplier(loadVersionFor(() -> new DownloadListPage(CurseForgeRemoteModRepository.WORLDS)));
|
||||
tab = new TabHeader(transitionPane, newGameTab, modpackTab, modTab, resourcePackTab, shaderTab, worldTab);
|
||||
|
||||
@@ -129,7 +129,7 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
};
|
||||
}
|
||||
|
||||
public static void download(Profile profile, @Nullable String version, RemoteMod.Version file, String subdirectoryName) {
|
||||
public static void download(DownloadProvider downloadProvider, Profile profile, @Nullable String version, RemoteMod.Version file, String subdirectoryName) {
|
||||
if (version == null) version = profile.getSelectedVersion();
|
||||
|
||||
Path runDirectory = profile.getRepository().hasVersion(version) ? profile.getRepository().getRunDirectory(version) : profile.getRepository().getBaseDirectory();
|
||||
@@ -138,7 +138,7 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
Path dest = runDirectory.resolve(subdirectoryName).resolve(result);
|
||||
|
||||
Controllers.taskDialog(Task.composeAsync(() -> {
|
||||
var task = new FileDownloadTask(file.getFile().getUrl(), dest);
|
||||
var task = new FileDownloadTask(downloadProvider.injectURLWithCandidates(file.getFile().getUrl()), dest);
|
||||
task.setName(file.getName());
|
||||
return task;
|
||||
}).whenComplete(Schedulers.javafx(), exception -> {
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.jackhuang.hmcl.ui.construct.RequiredValidator;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.util.SettingsMap;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
|
||||
import static javafx.beans.binding.Bindings.createBooleanBinding;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
@@ -50,11 +51,11 @@ public class InstallersPage extends AbstractInstallersPage {
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return group.getGame().versionProperty().get().version();
|
||||
return ((RemoteVersion) controller.getSettings().get("game")).getGameVersion();
|
||||
}
|
||||
|
||||
private String getVersion(String id) {
|
||||
return ((RemoteVersion) controller.getSettings().get(id)).getSelfVersion();
|
||||
return I18n.getDisplayVersion((RemoteVersion) controller.getSettings().get(id));
|
||||
}
|
||||
|
||||
protected void reload() {
|
||||
@@ -111,7 +112,7 @@ public class InstallersPage extends AbstractInstallersPage {
|
||||
}
|
||||
|
||||
private void setTxtNameWithLoaders() {
|
||||
StringBuilder nameBuilder = new StringBuilder(group.getGame().versionProperty().get().version());
|
||||
StringBuilder nameBuilder = new StringBuilder(getTitle());
|
||||
|
||||
for (InstallerItem library : group.getLibraries()) {
|
||||
String libraryId = library.getLibraryId().replace(LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId(), "");
|
||||
@@ -120,23 +121,22 @@ public class InstallersPage extends AbstractInstallersPage {
|
||||
}
|
||||
|
||||
LibraryAnalyzer.LibraryType libraryType = LibraryAnalyzer.LibraryType.fromPatchId(libraryId);
|
||||
|
||||
if (libraryType != null) {
|
||||
String loaderName = switch (libraryType) {
|
||||
case FORGE -> i18n("install.installer.forge");
|
||||
case NEO_FORGE -> i18n("install.installer.neoforge");
|
||||
case CLEANROOM -> i18n("install.installer.cleanroom");
|
||||
case LEGACY_FABRIC -> i18n("install.installer.legacyfabric").replace(" ", "_");
|
||||
case FABRIC -> i18n("install.installer.fabric");
|
||||
case LITELOADER -> i18n("install.installer.liteloader");
|
||||
case QUILT -> i18n("install.installer.quilt");
|
||||
case OPTIFINE -> i18n("install.installer.optifine");
|
||||
case FORGE -> "Forge";
|
||||
case NEO_FORGE -> "NeoForge";
|
||||
case CLEANROOM -> "Cleanroom";
|
||||
case LEGACY_FABRIC -> "LegacyFabric";
|
||||
case FABRIC -> "Fabric";
|
||||
case LITELOADER -> "LiteLoader";
|
||||
case QUILT -> "Quilt";
|
||||
case OPTIFINE -> "OptiFine";
|
||||
default -> null;
|
||||
};
|
||||
|
||||
if (loaderName == null)
|
||||
continue;
|
||||
|
||||
nameBuilder.append("-").append(loaderName);
|
||||
if (loaderName != null)
|
||||
nameBuilder.append('-').append(loaderName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ public final class ModpackInstallWizardProvider implements WizardProvider {
|
||||
private final Path file;
|
||||
private final String updateVersion;
|
||||
private String iconUrl;
|
||||
private boolean hasSource;
|
||||
|
||||
public ModpackInstallWizardProvider(Profile profile) {
|
||||
this(profile, null, null);
|
||||
@@ -79,6 +80,7 @@ public final class ModpackInstallWizardProvider implements WizardProvider {
|
||||
if (StringUtils.isNotBlank(iconUrl))
|
||||
settings.put(LocalModpackPage.MODPACK_ICON_URL, iconUrl);
|
||||
settings.put(ModpackPage.PROFILE, profile);
|
||||
hasSource = settings.containsKey(LocalModpackPage.MODPACK_FILE) || settings.containsKey(RemoteModpackPage.MODPACK_SERVER_MANIFEST);
|
||||
}
|
||||
|
||||
private Task<?> finishModpackInstallingAsync(SettingsMap settings) {
|
||||
@@ -156,7 +158,6 @@ public final class ModpackInstallWizardProvider implements WizardProvider {
|
||||
|
||||
@Override
|
||||
public Node createPage(WizardController controller, int step, SettingsMap settings) {
|
||||
boolean hasSource = controller.getSettings().containsKey(LocalModpackPage.MODPACK_FILE) || settings.containsKey(RemoteModpackPage.MODPACK_SERVER_MANIFEST);
|
||||
if (hasSource) {
|
||||
return switch (step) {
|
||||
case 0 -> createModpackInstallPage(controller);
|
||||
|
||||
@@ -42,11 +42,11 @@ public abstract class ModpackPage extends SpinnerPane implements WizardPage {
|
||||
{
|
||||
var archiveNamePane = new LinePane();
|
||||
{
|
||||
archiveNamePane.setTitle(i18n("archive.file.name"));
|
||||
archiveNamePane.setTitle(i18n("version.name"));
|
||||
|
||||
txtModpackName = new JFXTextField();
|
||||
txtModpackName.setPrefWidth(300);
|
||||
// FIXME: Validator are not shown properly
|
||||
FXUtils.setLimitHeight(archiveNamePane, 75);
|
||||
// BorderPane.setMargin(txtModpackName, new Insets(0, 0, 8, 32));
|
||||
BorderPane.setAlignment(txtModpackName, Pos.CENTER_RIGHT);
|
||||
archiveNamePane.setRight(txtModpackName);
|
||||
|
||||
@@ -144,8 +144,6 @@ public final class ModpackSelectionPage extends VBox implements WizardPage {
|
||||
// otherwise we still consider the file as modpack zip file
|
||||
// since casually the url may not ends with ".zip"
|
||||
Path modpack = Files.createTempFile("modpack", ".zip");
|
||||
handler.resolve();
|
||||
|
||||
Controllers.taskDialog(
|
||||
new FileDownloadTask(url, modpack)
|
||||
.whenComplete(Schedulers.javafx(), e -> {
|
||||
|
||||
@@ -278,10 +278,12 @@ public final class VersionsPage extends Control implements WizardPage, Refreshab
|
||||
iconType = VersionIconType.COMMAND;
|
||||
|
||||
imageView.setImage(iconType.getIcon());
|
||||
String displayGameVersion = I18n.getDisplayVersion(GameVersionNumber.asGameVersion(remoteVersion.getGameVersion()));
|
||||
|
||||
if (twoLineListItem.getSubtitle() == null)
|
||||
twoLineListItem.setSubtitle(remoteVersion.getGameVersion());
|
||||
twoLineListItem.setSubtitle(displayGameVersion);
|
||||
else
|
||||
twoLineListItem.addTag(remoteVersion.getGameVersion());
|
||||
twoLineListItem.addTag(displayGameVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,10 +221,15 @@ public final class ModpackInfoPage extends Control implements WizardPage {
|
||||
|
||||
list.getContent().addAll(
|
||||
createTextFieldLinePane(i18n("modpack.name"), skinnable.name, new RequiredValidator()),
|
||||
createTextFieldLinePane(i18n("archive.author"), skinnable.author, new RequiredValidator()),
|
||||
createTextFieldLinePane(i18n("archive.version"), skinnable.version, new RequiredValidator())
|
||||
);
|
||||
|
||||
if (skinnable.options.isRequireAuthor()) {
|
||||
list.getContent().add(
|
||||
createTextFieldLinePane(i18n("archive.author"), skinnable.author, new RequiredValidator())
|
||||
);
|
||||
}
|
||||
|
||||
if (skinnable.options.isRequireFileApi()) {
|
||||
list.getContent().add(createTextFieldLinePane(
|
||||
i18n("modpack.file_api"), skinnable.fileApi,
|
||||
|
||||
@@ -241,9 +241,7 @@ public final class JavaManagementPage extends ListPageBase<JavaRuntime> {
|
||||
HBox right = new HBox();
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
{
|
||||
JFXButton revealButton = new JFXButton();
|
||||
revealButton.setGraphic(SVG.FOLDER_OPEN.createIcon());
|
||||
revealButton.getStyleClass().add("toggle-icon4");
|
||||
JFXButton revealButton = FXUtils.newToggleButton4(SVG.FOLDER_OPEN);
|
||||
revealButton.setOnAction(e -> {
|
||||
JavaRuntime java = getItem();
|
||||
if (java != null)
|
||||
|
||||
@@ -157,26 +157,20 @@ public final class JavaRestorePage extends ListPageBase<JavaRestorePage.Disabled
|
||||
HBox right = new HBox();
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
{
|
||||
JFXButton revealButton = new JFXButton();
|
||||
revealButton.getStyleClass().add("toggle-icon4");
|
||||
revealButton.setGraphic(SVG.FOLDER_OPEN.createIcon());
|
||||
JFXButton revealButton = FXUtils.newToggleButton4(SVG.FOLDER_OPEN);
|
||||
revealButton.setOnAction(e -> skinnable.onReveal());
|
||||
FXUtils.installFastTooltip(revealButton, i18n("reveal.in_file_manager"));
|
||||
|
||||
if (skinnable.realPath == null) {
|
||||
revealButton.setDisable(true);
|
||||
|
||||
JFXButton removeButton = new JFXButton();
|
||||
removeButton.getStyleClass().add("toggle-icon4");
|
||||
removeButton.setGraphic(SVG.DELETE.createIcon());
|
||||
JFXButton removeButton = FXUtils.newToggleButton4(SVG.DELETE_FOREVER);
|
||||
removeButton.setOnAction(e -> skinnable.onRemove());
|
||||
FXUtils.installFastTooltip(removeButton, i18n("java.disabled.management.remove"));
|
||||
|
||||
right.getChildren().setAll(revealButton, removeButton);
|
||||
} else {
|
||||
JFXButton restoreButton = new JFXButton();
|
||||
restoreButton.getStyleClass().add("toggle-icon4");
|
||||
restoreButton.setGraphic(SVG.RESTORE.createIcon());
|
||||
JFXButton restoreButton = FXUtils.newToggleButton4(SVG.RESTORE);
|
||||
restoreButton.setOnAction(e -> skinnable.onRestore());
|
||||
FXUtils.installFastTooltip(restoreButton, i18n("java.disabled.management.restore"));
|
||||
|
||||
|
||||
@@ -227,7 +227,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
launchLabel.setText(i18n("version.launch.empty"));
|
||||
currentLabel.setText(null);
|
||||
graphic.getChildren().setAll(launchLabel);
|
||||
launchButton.setOnAction(e -> MainPage.this.launchNoGame());
|
||||
FXUtils.setOnActionWithCooldown(launchButton, MainPage.this::launchNoGame);
|
||||
if (tooltip == null)
|
||||
tooltip = new Tooltip(i18n("version.launch.empty.tooltip"));
|
||||
FXUtils.installFastTooltip(launchButton, tooltip);
|
||||
@@ -235,7 +235,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
launchLabel.setText(i18n("version.launch"));
|
||||
currentLabel.setText(currentGame);
|
||||
graphic.getChildren().setAll(launchLabel, currentLabel);
|
||||
launchButton.setOnAction(e -> MainPage.this.launch());
|
||||
FXUtils.setOnActionWithCooldown(launchButton, MainPage.this::launch);
|
||||
if (tooltip != null)
|
||||
Tooltip.uninstall(launchButton, tooltip);
|
||||
}
|
||||
@@ -277,7 +277,9 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
private void showUpdate(boolean show) {
|
||||
doAnimation(show);
|
||||
|
||||
if (show && getLatestVersion() != null && !Objects.equals(config().getPromptedVersion(), getLatestVersion().getVersion())) {
|
||||
if (show && !config().isDisableAutoShowUpdateDialog()
|
||||
&& getLatestVersion() != null
|
||||
&& !Objects.equals(config().getPromptedVersion(), getLatestVersion().getVersion())) {
|
||||
Controllers.dialog(new MessageDialogPane.Builder("", i18n("update.bubble.title", getLatestVersion().getVersion()), MessageDialogPane.MessageType.INFO)
|
||||
.addAction(i18n("button.view"), () -> {
|
||||
config().setPromptedVersion(getLatestVersion().getVersion());
|
||||
|
||||
@@ -226,9 +226,7 @@ public class PersonalizationPage extends StackPane {
|
||||
.fallbackTo(12.0)
|
||||
.asPredicate(Validator.addTo(txtLogFontSize)));
|
||||
|
||||
JFXButton clearButton = new JFXButton();
|
||||
clearButton.getStyleClass().add("toggle-icon4");
|
||||
clearButton.setGraphic(SVG.RESTORE.createIcon());
|
||||
JFXButton clearButton = FXUtils.newToggleButton4(SVG.RESTORE);
|
||||
clearButton.setOnAction(e -> cboLogFont.setValue(null));
|
||||
|
||||
FXUtils.installFastTooltip(clearButton, i18n("button.reset"));
|
||||
@@ -276,9 +274,7 @@ public class PersonalizationPage extends StackPane {
|
||||
cboFont.setValue(config().getLauncherFontFamily());
|
||||
FXUtils.onChange(cboFont.valueProperty(), FontManager::setFontFamily);
|
||||
|
||||
JFXButton clearButton = new JFXButton();
|
||||
clearButton.getStyleClass().add("toggle-icon4");
|
||||
clearButton.setGraphic(SVG.RESTORE.createIcon());
|
||||
JFXButton clearButton = FXUtils.newToggleButton4(SVG.RESTORE);
|
||||
clearButton.setOnAction(e -> cboFont.setValue(null));
|
||||
|
||||
FXUtils.installFastTooltip(clearButton, i18n("button.reset"));
|
||||
|
||||
@@ -45,6 +45,7 @@ import org.jackhuang.hmcl.upgrade.RemoteVersion;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChannel;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateHandler;
|
||||
import org.jackhuang.hmcl.util.AprilFools;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.i18n.SupportedLocale;
|
||||
@@ -140,10 +141,8 @@ public final class SettingsPage extends ScrollPane {
|
||||
}
|
||||
|
||||
{
|
||||
JFXButton btnUpdate = new JFXButton();
|
||||
JFXButton btnUpdate = FXUtils.newToggleButton4(SVG.UPDATE, 20);
|
||||
btnUpdate.setOnAction(e -> onUpdate());
|
||||
btnUpdate.getStyleClass().add("toggle-icon4");
|
||||
btnUpdate.setGraphic(SVG.UPDATE.createIcon(20));
|
||||
FXUtils.installFastTooltip(btnUpdate, i18n("update.tooltip"));
|
||||
|
||||
updateListener = any -> {
|
||||
@@ -218,6 +217,22 @@ public final class SettingsPage extends ScrollPane {
|
||||
settingsPane.getContent().add(previewPane);
|
||||
}
|
||||
|
||||
{
|
||||
LineToggleButton disableAutoShowUpdateDialogPane = new LineToggleButton();
|
||||
disableAutoShowUpdateDialogPane.setTitle(i18n("update.disable_auto_show_update_dialog"));
|
||||
disableAutoShowUpdateDialogPane.setSubtitle(i18n("update.disable_auto_show_update_dialog.subtitle"));
|
||||
disableAutoShowUpdateDialogPane.selectedProperty().bindBidirectional(config().disableAutoShowUpdateDialogProperty());
|
||||
settingsPane.getContent().add(disableAutoShowUpdateDialogPane);
|
||||
}
|
||||
|
||||
if (AprilFools.isShowAprilFoolsSettings()) {
|
||||
LineToggleButton disableAprilFools = new LineToggleButton();
|
||||
disableAprilFools.setTitle(i18n("settings.launcher.disable_april_fools"));
|
||||
disableAprilFools.setSubtitle(i18n("settings.take_effect_after_restart"));
|
||||
disableAprilFools.selectedProperty().bindBidirectional(config().disableAprilFoolsProperty());
|
||||
settingsPane.getContent().add(disableAprilFools);
|
||||
}
|
||||
|
||||
{
|
||||
MultiFileItem<EnumCommonDirectory> fileCommonLocation = new MultiFileItem<>();
|
||||
fileCommonLocation.loadChildren(Arrays.asList(
|
||||
@@ -273,6 +288,15 @@ public final class SettingsPage extends ScrollPane {
|
||||
settingsPane.getContent().add(disableAutoGameOptionsPane);
|
||||
}
|
||||
|
||||
{
|
||||
LineToggleButton allowAutoAgentPane = new LineToggleButton();
|
||||
allowAutoAgentPane.setTitle(i18n("settings.launcher.allow_auto_agent"));
|
||||
allowAutoAgentPane.setSubtitle(i18n("settings.launcher.allow_auto_agent.subtitle"));
|
||||
allowAutoAgentPane.selectedProperty().bindBidirectional(config().allowAutoAgentProperty());
|
||||
|
||||
settingsPane.getContent().add(allowAutoAgentPane);
|
||||
}
|
||||
|
||||
{
|
||||
BorderPane debugPane = new BorderPane();
|
||||
|
||||
|
||||
@@ -60,11 +60,9 @@ public class ProfileListItemSkin extends SkinBase<ProfileListItem> {
|
||||
HBox right = new HBox();
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
JFXButton btnRemove = new JFXButton();
|
||||
JFXButton btnRemove = FXUtils.newToggleButton4(SVG.CLOSE, 14);
|
||||
btnRemove.setOnAction(e -> skinnable.remove());
|
||||
btnRemove.getStyleClass().add("toggle-icon4");
|
||||
BorderPane.setAlignment(btnRemove, Pos.CENTER);
|
||||
btnRemove.setGraphic(SVG.CLOSE.createIcon(14));
|
||||
right.getChildren().add(btnRemove);
|
||||
root.setRight(right);
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ import javafx.scene.Node;
|
||||
import javafx.scene.control.SelectionMode;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
@@ -50,10 +49,7 @@ import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentList;
|
||||
import org.jackhuang.hmcl.ui.construct.MDListCell;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -114,7 +110,7 @@ final class DatapackListPageSkin extends SkinBase<DatapackListPage> {
|
||||
createToolbarButton2(i18n("search"), SVG.SEARCH, () -> isSearching.set(true))
|
||||
);
|
||||
|
||||
JFXButton removeButton = createToolbarButton2(i18n("button.remove"), SVG.DELETE, () -> {
|
||||
JFXButton removeButton = createToolbarButton2(i18n("button.remove"), SVG.DELETE_FOREVER, () -> {
|
||||
Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), () -> {
|
||||
skinnable.removeSelected(listView.getSelectionModel().getSelectedItems());
|
||||
}, null);
|
||||
@@ -274,20 +270,20 @@ final class DatapackListPageSkin extends SkinBase<DatapackListPage> {
|
||||
}
|
||||
}
|
||||
|
||||
public void loadIcon(ImageView imageView, @Nullable WeakReference<ObjectProperty<DatapackInfoObject>> current) {
|
||||
public void loadIcon(ImageContainer imageContainer, @Nullable WeakReference<ObjectProperty<DatapackInfoObject>> current) {
|
||||
SoftReference<CompletableFuture<Image>> iconCache = this.iconCache;
|
||||
CompletableFuture<Image> imageFuture;
|
||||
if (iconCache != null && (imageFuture = iconCache.get()) != null) {
|
||||
Image image = imageFuture.getNow(null);
|
||||
if (image != null) {
|
||||
imageView.setImage(image);
|
||||
imageContainer.setImage(image);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
imageFuture = CompletableFuture.supplyAsync(this::loadIcon, Schedulers.io());
|
||||
this.iconCache = new SoftReference<>(imageFuture);
|
||||
}
|
||||
imageView.setImage(FXUtils.newBuiltinImage("/assets/img/unknown_pack.png"));
|
||||
imageContainer.setImage(FXUtils.newBuiltinImage("/assets/img/unknown_pack.png"));
|
||||
imageFuture.thenAcceptAsync(image -> {
|
||||
if (current != null) {
|
||||
ObjectProperty<DatapackInfoObject> infoObjectProperty = current.get();
|
||||
@@ -296,7 +292,7 @@ final class DatapackListPageSkin extends SkinBase<DatapackListPage> {
|
||||
return;
|
||||
}
|
||||
}
|
||||
imageView.setImage(image);
|
||||
imageContainer.setImage(image);
|
||||
}, Schedulers.javafx());
|
||||
|
||||
}
|
||||
@@ -304,7 +300,7 @@ final class DatapackListPageSkin extends SkinBase<DatapackListPage> {
|
||||
|
||||
private final class DatapackInfoListCell extends MDListCell<DatapackInfoObject> {
|
||||
final JFXCheckBox checkBox = new JFXCheckBox();
|
||||
ImageView imageView = new ImageView();
|
||||
ImageContainer imageContainer = new ImageContainer(32);
|
||||
final TwoLineListItem content = new TwoLineListItem();
|
||||
BooleanProperty booleanProperty;
|
||||
|
||||
@@ -320,13 +316,10 @@ final class DatapackListPageSkin extends SkinBase<DatapackListPage> {
|
||||
|
||||
checkBox.disableProperty().bind(isReadOnlyProperty);
|
||||
|
||||
imageView.setFitWidth(32);
|
||||
imageView.setFitHeight(32);
|
||||
imageView.setPreserveRatio(true);
|
||||
imageView.setImage(FXUtils.newBuiltinImage("/assets/img/unknown_pack.png"));
|
||||
imageContainer.setImage(FXUtils.newBuiltinImage("/assets/img/unknown_pack.png"));
|
||||
|
||||
StackPane.setMargin(container, new Insets(8));
|
||||
container.getChildren().setAll(checkBox, imageView, content);
|
||||
container.getChildren().setAll(checkBox, imageContainer, content);
|
||||
getContainer().getChildren().setAll(container);
|
||||
|
||||
getContainer().getParent().addEventHandler(MouseEvent.MOUSE_PRESSED, mouseEvent -> handleSelect(this, mouseEvent));
|
||||
@@ -341,7 +334,7 @@ final class DatapackListPageSkin extends SkinBase<DatapackListPage> {
|
||||
checkBox.selectedProperty().unbindBidirectional(booleanProperty);
|
||||
}
|
||||
checkBox.selectedProperty().bindBidirectional(booleanProperty = dataItem.activeProperty);
|
||||
dataItem.loadIcon(imageView, new WeakReference<>(this.itemProperty()));
|
||||
dataItem.loadIcon(imageContainer, new WeakReference<>(this.itemProperty()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.*;
|
||||
@@ -50,10 +49,7 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||
import org.jackhuang.hmcl.ui.construct.NoneMultipleSelectionModel;
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
@@ -105,6 +101,10 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
this.downloadProvider = DownloadProviders.getDownloadProvider();
|
||||
}
|
||||
|
||||
public DownloadProvider getDownloadProvider() {
|
||||
return downloadProvider;
|
||||
}
|
||||
|
||||
public ObservableList<Node> getActions() {
|
||||
return actions;
|
||||
}
|
||||
@@ -237,18 +237,20 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
|
||||
private static class ModDownloadListPageSkin extends SkinBase<DownloadListPage> {
|
||||
private final JFXListView<RemoteMod> listView = new JFXListView<>();
|
||||
private final RemoteImageLoader iconLoader = new RemoteImageLoader() {
|
||||
@Override
|
||||
protected @NotNull Task<Image> createLoadTask(@NotNull URI uri) {
|
||||
return FXUtils.getRemoteImageTask(uri, 80, 80, true, true);
|
||||
}
|
||||
};
|
||||
private final RemoteImageLoader iconLoader;
|
||||
|
||||
protected ModDownloadListPageSkin(DownloadListPage control) {
|
||||
super(control);
|
||||
|
||||
listView.getStyleClass().add("no-horizontal-scrollbar");
|
||||
|
||||
iconLoader = new RemoteImageLoader(control.downloadProvider) {
|
||||
@Override
|
||||
protected @NotNull Task<Image> createLoadTask(@NotNull List<URI> uris) {
|
||||
return FXUtils.getRemoteImageTask(uris, 80, 80, true, true);
|
||||
}
|
||||
};
|
||||
|
||||
BorderPane pane = new BorderPane();
|
||||
|
||||
GridPane searchPane = new GridPane();
|
||||
@@ -537,7 +539,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
private final StackPane wrapper = new StackPane();
|
||||
|
||||
private final TwoLineListItem content = new TwoLineListItem();
|
||||
private final ImageView imageView = new ImageView();
|
||||
private final ImageContainer imageContainer = new ImageContainer(40);
|
||||
|
||||
{
|
||||
setPadding(PADDING);
|
||||
@@ -547,11 +549,9 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
container.setCursor(Cursor.HAND);
|
||||
container.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
imageView.setFitWidth(40);
|
||||
imageView.setFitHeight(40);
|
||||
imageView.setMouseTransparent(true);
|
||||
imageContainer.setMouseTransparent(true);
|
||||
|
||||
container.getChildren().setAll(FXUtils.limitingSize(imageView, 40, 40), content);
|
||||
container.getChildren().setAll(imageContainer, content);
|
||||
HBox.setHgrow(content, Priority.ALWAYS);
|
||||
|
||||
this.graphic = new RipplerContainer(container);
|
||||
@@ -584,7 +584,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
if (getSkinnable().shouldDisplayCategory(category))
|
||||
content.addTag(getSkinnable().getLocalizedCategory(category));
|
||||
}
|
||||
iconLoader.load(imageView.imageProperty(), item.getIconUrl());
|
||||
iconLoader.load(imageContainer.imageProperty(), item.getIconUrl());
|
||||
setGraphic(wrapper);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,14 +32,13 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
@@ -114,7 +113,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
setFailed(false);
|
||||
|
||||
Task.supplyAsync(() -> {
|
||||
Stream<RemoteMod.Version> versions = addon.getData().loadVersions(repository);
|
||||
Stream<RemoteMod.Version> versions = addon.getData().loadVersions(repository, page.getDownloadProvider());
|
||||
return sortVersions(versions);
|
||||
}).whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
if (exception == null) {
|
||||
@@ -181,7 +180,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
if (this.callback == null) {
|
||||
saveAs(mod, file);
|
||||
} else {
|
||||
this.callback.download(version.getProfile(), version.getVersion(), mod, file);
|
||||
this.callback.download(page.getDownloadProvider(), version.getProfile(), version.getVersion(), mod, file);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,25 +224,18 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
VBox pane = new VBox(8);
|
||||
pane.getStyleClass().add("gray-background");
|
||||
pane.setPadding(new Insets(10));
|
||||
ScrollPane scrollPane = new ScrollPane(pane);
|
||||
FXUtils.smoothScrolling(scrollPane);
|
||||
scrollPane.setFitToWidth(true);
|
||||
scrollPane.setFitToHeight(true);
|
||||
|
||||
HBox descriptionPane = new HBox(8);
|
||||
descriptionPane.setMinHeight(Region.USE_PREF_SIZE);
|
||||
descriptionPane.setAlignment(Pos.CENTER);
|
||||
pane.getChildren().add(descriptionPane);
|
||||
descriptionPane.getStyleClass().add("card-non-transparent");
|
||||
BorderPane.setMargin(descriptionPane, new Insets(11, 11, 0, 11));
|
||||
{
|
||||
ImageView imageView = new ImageView();
|
||||
imageView.setFitWidth(40);
|
||||
imageView.setFitHeight(40);
|
||||
var imageContainer = new ImageContainer(40);
|
||||
if (StringUtils.isNotBlank(getSkinnable().addon.getIconUrl())) {
|
||||
imageView.imageProperty().bind(FXUtils.newRemoteImage(getSkinnable().addon.getIconUrl(), 80, 80, true, true));
|
||||
imageContainer.imageProperty().bind(FXUtils.newRemoteImage(getSkinnable().addon.getIconUrl(), 80, 80, true, true));
|
||||
}
|
||||
descriptionPane.getChildren().add(FXUtils.limitingSize(imageView, 40, 40));
|
||||
descriptionPane.getChildren().add(imageContainer);
|
||||
|
||||
TwoLineListItem content = new TwoLineListItem();
|
||||
HBox.setHgrow(content, Priority.ALWAYS);
|
||||
@@ -261,14 +253,12 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
openMcmodButton.setExternalLink(getSkinnable().translations.getMcmodUrl(getSkinnable().mod));
|
||||
descriptionPane.getChildren().add(openMcmodButton);
|
||||
openMcmodButton.setMinWidth(Region.USE_PREF_SIZE);
|
||||
FXUtils.installFastTooltip(openMcmodButton, i18n("mods.mcmod"));
|
||||
}
|
||||
|
||||
JFXHyperlink openUrlButton = new JFXHyperlink(control.page.getLocalizedOfficialPage());
|
||||
openUrlButton.setExternalLink(getSkinnable().addon.getPageUrl());
|
||||
descriptionPane.getChildren().add(openUrlButton);
|
||||
openUrlButton.setMinWidth(Region.USE_PREF_SIZE);
|
||||
FXUtils.installFastTooltip(openUrlButton, control.page.getLocalizedOfficialPage());
|
||||
}
|
||||
|
||||
SpinnerPane spinnerPane = new SpinnerPane();
|
||||
@@ -286,8 +276,13 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
spinnerPane.setOnFailedAction(e -> getSkinnable().loadModVersions());
|
||||
|
||||
ComponentList list = new ComponentList();
|
||||
StackPane.setAlignment(list, Pos.TOP_CENTER);
|
||||
spinnerPane.setContent(list);
|
||||
ScrollPane scrollPane = new ScrollPane(list);
|
||||
FXUtils.smoothScrolling(scrollPane);
|
||||
scrollPane.setFitToWidth(true);
|
||||
scrollPane.setFitToHeight(true);
|
||||
FXUtils.setOverflowHidden(scrollPane, 8);
|
||||
StackPane.setAlignment(scrollPane, Pos.TOP_CENTER);
|
||||
spinnerPane.setContent(scrollPane);
|
||||
|
||||
FXUtils.onChangeAndOperate(control.loaded, loaded -> {
|
||||
if (control.versions == null) return;
|
||||
@@ -341,7 +336,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
});
|
||||
}
|
||||
|
||||
getChildren().setAll(scrollPane);
|
||||
getChildren().setAll(pane);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,10 +357,8 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
pane.setAlignment(Pos.CENTER_LEFT);
|
||||
TwoLineListItem content = new TwoLineListItem();
|
||||
HBox.setHgrow(content, Priority.ALWAYS);
|
||||
ImageView imageView = new ImageView();
|
||||
imageView.setFitWidth(40);
|
||||
imageView.setFitHeight(40);
|
||||
pane.getChildren().setAll(FXUtils.limitingSize(imageView, 40, 40), content);
|
||||
var imageView = new ImageContainer(40);
|
||||
pane.getChildren().setAll(imageView, content);
|
||||
|
||||
RipplerContainer container = new RipplerContainer(pane);
|
||||
FXUtils.onClicked(container, () -> {
|
||||
@@ -493,6 +486,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
scrollPane.setFitToWidth(true);
|
||||
scrollPane.setFitToHeight(true);
|
||||
FXUtils.smoothScrolling(scrollPane);
|
||||
FXUtils.setOverflowHidden(scrollPane, 8);
|
||||
spinnerPane.setContent(scrollPane);
|
||||
box.getChildren().add(spinnerPane);
|
||||
VBox.setVgrow(spinnerPane, Priority.SOMETIMES);
|
||||
@@ -555,7 +549,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
dependencies.put(dependency.getType(), list);
|
||||
}
|
||||
|
||||
queue.add(Task.supplyAsync(Schedulers.io(), dependency::load)
|
||||
queue.add(Task.supplyAsync(Schedulers.io(), () -> dependency.load(selfPage.page.getDownloadProvider()))
|
||||
.setSignificance(Task.TaskSignificance.MINOR)
|
||||
.thenAcceptAsync(Schedulers.javafx(), dep -> {
|
||||
if (dep == RemoteMod.BROKEN) {
|
||||
@@ -583,7 +577,8 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface DownloadCallback {
|
||||
void download(Profile profile, @Nullable String version, RemoteMod mod, RemoteMod.Version file);
|
||||
void download(DownloadProvider downloadProvider, Profile profile, @Nullable String version, RemoteMod mod, RemoteMod.Version file);
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,6 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.image.ImageView;
|
||||
import org.jackhuang.hmcl.event.Event;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
@@ -27,32 +25,26 @@ import org.jackhuang.hmcl.setting.VersionIconType;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
|
||||
import org.jackhuang.hmcl.ui.construct.ImageContainer;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class GameAdvancedListItem extends AdvancedListItem {
|
||||
private final ImageView imageView;
|
||||
private final ImageContainer imageContainer;
|
||||
private final WeakListenerHolder holder = new WeakListenerHolder();
|
||||
private Profile profile;
|
||||
@SuppressWarnings("unused")
|
||||
private Consumer<Event> onVersionIconChangedListener;
|
||||
|
||||
@SuppressWarnings("SuspiciousNameCombination")
|
||||
public GameAdvancedListItem() {
|
||||
this.imageView = new ImageView();
|
||||
FXUtils.limitSize(imageView, LEFT_GRAPHIC_SIZE, LEFT_GRAPHIC_SIZE);
|
||||
imageView.setPreserveRatio(true);
|
||||
imageView.setImage(null);
|
||||
|
||||
Node imageViewWrapper = FXUtils.limitingSize(imageView, LEFT_GRAPHIC_SIZE, LEFT_GRAPHIC_SIZE);
|
||||
imageView.setMouseTransparent(true);
|
||||
AdvancedListItem.setAlignment(imageViewWrapper, Pos.CENTER);
|
||||
setLeftGraphic(imageViewWrapper);
|
||||
this.imageContainer = new ImageContainer(LEFT_GRAPHIC_SIZE);
|
||||
imageContainer.setMouseTransparent(true);
|
||||
AdvancedListItem.setAlignment(imageContainer, Pos.CENTER);
|
||||
setLeftGraphic(imageContainer);
|
||||
|
||||
holder.add(FXUtils.onWeakChangeAndOperate(Profiles.selectedVersionProperty(), this::loadVersion));
|
||||
|
||||
}
|
||||
|
||||
private void loadVersion(String version) {
|
||||
@@ -68,11 +60,11 @@ public class GameAdvancedListItem extends AdvancedListItem {
|
||||
Profiles.getSelectedProfile().getRepository().hasVersion(version)) {
|
||||
setTitle(i18n("version.manage.manage"));
|
||||
setSubtitle(version);
|
||||
imageView.setImage(Profiles.getSelectedProfile().getRepository().getVersionIconImage(version));
|
||||
imageContainer.setImage(Profiles.getSelectedProfile().getRepository().getVersionIconImage(version));
|
||||
} else {
|
||||
setTitle(i18n("version.empty"));
|
||||
setSubtitle(i18n("version.empty.add"));
|
||||
imageView.setImage(VersionIconType.DEFAULT.getIcon());
|
||||
imageContainer.setImage(VersionIconType.DEFAULT.getIcon());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,6 @@ import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
@@ -42,7 +41,7 @@ public final class GameListCell extends ListCell<GameListItem> {
|
||||
|
||||
private final Region graphic;
|
||||
|
||||
private final ImageView imageView;
|
||||
private final ImageContainer imageView;
|
||||
private final TwoLineListItem content;
|
||||
|
||||
private final JFXRadioButton chkSelected;
|
||||
@@ -87,8 +86,7 @@ public final class GameListCell extends ListCell<GameListItem> {
|
||||
center.setSpacing(8);
|
||||
center.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
this.imageView = new ImageView();
|
||||
FXUtils.limitSize(imageView, 32, 32);
|
||||
this.imageView = new ImageContainer(32);
|
||||
|
||||
this.content = new TwoLineListItem();
|
||||
BorderPane.setAlignment(content, Pos.CENTER);
|
||||
@@ -108,30 +106,26 @@ public final class GameListCell extends ListCell<GameListItem> {
|
||||
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
this.btnUpgrade = new JFXButton();
|
||||
this.btnUpgrade = FXUtils.newToggleButton4(SVG.UPDATE);
|
||||
btnUpgrade.setOnAction(e -> {
|
||||
GameListItem item = this.getItem();
|
||||
if (item != null)
|
||||
item.update();
|
||||
});
|
||||
btnUpgrade.getStyleClass().add("toggle-icon4");
|
||||
btnUpgrade.setGraphic(SVG.UPDATE.createIcon());
|
||||
FXUtils.installFastTooltip(btnUpgrade, i18n("version.update"));
|
||||
right.getChildren().add(btnUpgrade);
|
||||
|
||||
this.btnLaunch = new JFXButton();
|
||||
this.btnLaunch = FXUtils.newToggleButton4(SVG.ROCKET_LAUNCH);
|
||||
btnLaunch.setOnAction(e -> {
|
||||
GameListItem item = this.getItem();
|
||||
if (item != null)
|
||||
item.testGame();
|
||||
});
|
||||
btnLaunch.getStyleClass().add("toggle-icon4");
|
||||
BorderPane.setAlignment(btnLaunch, Pos.CENTER);
|
||||
btnLaunch.setGraphic(SVG.ROCKET_LAUNCH.createIcon());
|
||||
FXUtils.installFastTooltip(btnLaunch, i18n("version.launch.test"));
|
||||
right.getChildren().add(btnLaunch);
|
||||
|
||||
this.btnManage = new JFXButton();
|
||||
this.btnManage = FXUtils.newToggleButton4(SVG.MORE_VERT);
|
||||
btnManage.setOnAction(e -> {
|
||||
GameListItem item = this.getItem();
|
||||
if (item == null)
|
||||
@@ -141,9 +135,7 @@ public final class GameListCell extends ListCell<GameListItem> {
|
||||
JFXPopup.PopupVPosition vPosition = determineOptimalPopupPosition(root, popup);
|
||||
popup.show(root, vPosition, JFXPopup.PopupHPosition.RIGHT, 0, vPosition == JFXPopup.PopupVPosition.TOP ? root.getHeight() : -root.getHeight());
|
||||
});
|
||||
btnManage.getStyleClass().add("toggle-icon4");
|
||||
BorderPane.setAlignment(btnManage, Pos.CENTER);
|
||||
btnManage.setGraphic(SVG.MORE_VERT.createIcon());
|
||||
FXUtils.installFastTooltip(btnManage, i18n("settings.game.management"));
|
||||
right.getChildren().add(btnManage);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
@@ -38,6 +37,7 @@ import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.ImageContainer;
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
@@ -87,7 +87,7 @@ public final class GameListPopupMenu extends StackPane {
|
||||
|
||||
private final Region graphic;
|
||||
|
||||
private final ImageView imageView;
|
||||
private final ImageContainer imageView;
|
||||
private final TwoLineListItem content;
|
||||
|
||||
private final StringProperty tag = new SimpleStringProperty();
|
||||
@@ -103,8 +103,7 @@ public final class GameListPopupMenu extends StackPane {
|
||||
FXUtils.setLimitWidth(imageViewContainer, 32);
|
||||
FXUtils.setLimitHeight(imageViewContainer, 32);
|
||||
|
||||
this.imageView = new ImageView();
|
||||
FXUtils.limitSize(imageView, 32, 32);
|
||||
this.imageView = new ImageContainer(32);
|
||||
imageViewContainer.getChildren().setAll(imageView);
|
||||
|
||||
this.content = new TwoLineListItem();
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ public final class HMCLLocalizedDownloadListPage extends DownloadListPage {
|
||||
}
|
||||
|
||||
public static DownloadListPage ofShaderPack(DownloadPage.DownloadCallback callback, boolean versionSelection) {
|
||||
var page = new HMCLLocalizedDownloadListPage(callback, versionSelection, RemoteModRepository.Type.SHADER_PACK, null, ModrinthRemoteModRepository.SHADER_PACKS);
|
||||
var page = new HMCLLocalizedDownloadListPage(callback, versionSelection, RemoteModRepository.Type.SHADER_PACK, CurseForgeRemoteModRepository.SHADERS, ModrinthRemoteModRepository.SHADER_PACKS);
|
||||
page.supportChinese.set(false);
|
||||
return page;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
@@ -30,16 +31,18 @@ import java.util.Objects;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public class ModCheckUpdatesTask extends Task<List<LocalModFile.ModUpdate>> {
|
||||
private final DownloadProvider downloadProvider;
|
||||
private final List<Task<LocalModFile.ModUpdate>> dependents;
|
||||
|
||||
public ModCheckUpdatesTask(String gameVersion, Collection<LocalModFile> mods) {
|
||||
public ModCheckUpdatesTask(DownloadProvider downloadProvider, String gameVersion, Collection<LocalModFile> mods) {
|
||||
this.downloadProvider = downloadProvider;
|
||||
dependents = mods.stream().map(mod ->
|
||||
Task.supplyAsync(Schedulers.io(), () -> {
|
||||
LocalModFile.ModUpdate candidate = null;
|
||||
for (RemoteMod.Type type : RemoteMod.Type.values()) {
|
||||
LocalModFile.ModUpdate update = null;
|
||||
try {
|
||||
update = mod.checkUpdates(gameVersion, type.getRemoteModRepository());
|
||||
update = mod.checkUpdates(downloadProvider, gameVersion, type.getRemoteModRepository());
|
||||
} catch (IOException e) {
|
||||
LOG.warning(String.format("Cannot check update for mod %s.", mod.getFileName()), e);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
import org.jackhuang.hmcl.mod.ModLoaderType;
|
||||
import org.jackhuang.hmcl.mod.ModManager;
|
||||
import org.jackhuang.hmcl.setting.DownloadProviders;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
@@ -43,6 +44,7 @@ import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
@@ -239,11 +241,12 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
.composeAsync(() -> {
|
||||
Optional<String> gameVersion = profile.getRepository().getGameVersion(instanceId);
|
||||
if (gameVersion.isPresent()) {
|
||||
return new ModCheckUpdatesTask(gameVersion.get(), mods);
|
||||
return new ModCheckUpdatesTask(DownloadProviders.getDownloadProvider(), gameVersion.get(), mods);
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
if (exception instanceof CancellationException) return;
|
||||
if (exception != null || result == null) {
|
||||
Controllers.dialog(i18n("mods.check_updates.failed_check"), i18n("message.failed"), MessageDialogPane.MessageType.ERROR);
|
||||
} else if (result.isEmpty()) {
|
||||
|
||||
@@ -30,7 +30,6 @@ import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.HBox;
|
||||
@@ -44,6 +43,7 @@ import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.mod.RemoteModRepository;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository;
|
||||
import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;
|
||||
import org.jackhuang.hmcl.setting.DownloadProviders;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.VersionIconType;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
@@ -155,7 +155,7 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
|
||||
// Toolbar Selecting
|
||||
toolbarSelecting.getChildren().setAll(
|
||||
createToolbarButton2(i18n("button.remove"), SVG.DELETE, () -> {
|
||||
createToolbarButton2(i18n("button.remove"), SVG.DELETE_FOREVER, () -> {
|
||||
Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), () -> {
|
||||
skinnable.removeSelected(listView.getSelectionModel().getSelectedItems());
|
||||
}, null);
|
||||
@@ -377,20 +377,20 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
return VersionIconType.getIconType(this.localModFile.getModLoaderType()).getIcon();
|
||||
}
|
||||
|
||||
public void loadIcon(ImageView imageView, @Nullable WeakReference<ObjectProperty<ModInfoObject>> current) {
|
||||
public void loadIcon(ImageContainer imageContainer, @Nullable WeakReference<ObjectProperty<ModInfoObject>> current) {
|
||||
SoftReference<CompletableFuture<Image>> iconCache = this.iconCache;
|
||||
CompletableFuture<Image> imageFuture;
|
||||
if (iconCache != null && (imageFuture = iconCache.get()) != null) {
|
||||
Image image = imageFuture.getNow(null);
|
||||
if (image != null) {
|
||||
imageView.setImage(image);
|
||||
imageContainer.setImage(image);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
imageFuture = CompletableFuture.supplyAsync(this::loadIcon, Schedulers.io());
|
||||
this.iconCache = new SoftReference<>(imageFuture);
|
||||
}
|
||||
imageView.setImage(VersionIconType.getIconType(localModFile.getModLoaderType()).getIcon());
|
||||
imageContainer.setImage(VersionIconType.getIconType(localModFile.getModLoaderType()).getIcon());
|
||||
imageFuture.thenAcceptAsync(image -> {
|
||||
if (current != null) {
|
||||
ObjectProperty<ModInfoObject> infoObjectProperty = current.get();
|
||||
@@ -400,7 +400,7 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
}
|
||||
}
|
||||
|
||||
imageView.setImage(image);
|
||||
imageContainer.setImage(image);
|
||||
}, Schedulers.javafx());
|
||||
}
|
||||
}
|
||||
@@ -414,9 +414,8 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
Stage stage = Controllers.getStage();
|
||||
maxWidthProperty().bind(stage.widthProperty().multiply(0.7));
|
||||
|
||||
ImageView imageView = new ImageView();
|
||||
FXUtils.limitSize(imageView, 40, 40);
|
||||
modInfo.loadIcon(imageView, null);
|
||||
var imageContainer = new ImageContainer(40);
|
||||
modInfo.loadIcon(imageContainer, null);
|
||||
|
||||
TwoLineListItem title = new TwoLineListItem();
|
||||
if (modInfo.getModTranslations() != null && I18n.isUseChinese())
|
||||
@@ -437,7 +436,7 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
}
|
||||
title.setSubtitle(subtitle.toString());
|
||||
|
||||
titleContainer.getChildren().setAll(FXUtils.limitingSize(imageView, 40, 40), title);
|
||||
titleContainer.getChildren().setAll(imageContainer, title);
|
||||
setHeading(titleContainer);
|
||||
|
||||
Label description = new Label(modInfo.getModInfo().getDescription().toString());
|
||||
@@ -467,7 +466,7 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
Task.runAsync(() -> {
|
||||
Optional<RemoteMod.Version> versionOptional = repository.getRemoteVersionByLocalFile(modInfo.getModInfo(), modInfo.getModInfo().getFile());
|
||||
if (versionOptional.isPresent()) {
|
||||
RemoteMod remoteMod = repository.getModById(versionOptional.get().getModid());
|
||||
RemoteMod remoteMod = repository.getModById(DownloadProviders.getDownloadProvider(), versionOptional.get().getModid());
|
||||
FXUtils.runInFX(() -> {
|
||||
for (ModLoaderType modLoaderType : versionOptional.get().getLoaders()) {
|
||||
String loaderName = switch (modLoaderType) {
|
||||
@@ -495,7 +494,7 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
repository instanceof CurseForgeRemoteModRepository ? HMCLLocalizedDownloadListPage.ofCurseForgeMod(null, false) : HMCLLocalizedDownloadListPage.ofModrinthMod(null, false),
|
||||
remoteMod,
|
||||
new Profile.ProfileVersion(ModListPageSkin.this.getSkinnable().getProfile(), ModListPageSkin.this.getSkinnable().getInstanceId()),
|
||||
(profile, version, mod, file) -> org.jackhuang.hmcl.ui.download.DownloadPage.download(profile, version, file, "mods")
|
||||
(downloadProvider, profile, version, mod, file) -> org.jackhuang.hmcl.ui.download.DownloadPage.download(downloadProvider, profile, version, file, "mods")
|
||||
));
|
||||
});
|
||||
button.setDisable(false);
|
||||
@@ -554,11 +553,11 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
private static final PseudoClass WARNING = PseudoClass.getPseudoClass("warning");
|
||||
|
||||
JFXCheckBox checkBox = new JFXCheckBox();
|
||||
ImageView imageView = new ImageView();
|
||||
ImageContainer imageContainer = new ImageContainer(24);
|
||||
TwoLineListItem content = new TwoLineListItem();
|
||||
JFXButton restoreButton = new JFXButton();
|
||||
JFXButton infoButton = new JFXButton();
|
||||
JFXButton revealButton = new JFXButton();
|
||||
JFXButton restoreButton = FXUtils.newToggleButton4(SVG.RESTORE);
|
||||
JFXButton infoButton = FXUtils.newToggleButton4(SVG.INFO);
|
||||
JFXButton revealButton = FXUtils.newToggleButton4(SVG.FOLDER);
|
||||
BooleanProperty booleanProperty;
|
||||
|
||||
Tooltip warningTooltip;
|
||||
@@ -575,23 +574,11 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
content.setMouseTransparent(true);
|
||||
setSelectable();
|
||||
|
||||
imageView.setFitWidth(24);
|
||||
imageView.setFitHeight(24);
|
||||
imageView.setPreserveRatio(true);
|
||||
imageView.setImage(VersionIconType.COMMAND.getIcon());
|
||||
|
||||
restoreButton.getStyleClass().add("toggle-icon4");
|
||||
restoreButton.setGraphic(SVG.RESTORE.createIcon());
|
||||
imageContainer.setImage(VersionIconType.COMMAND.getIcon());
|
||||
|
||||
FXUtils.installFastTooltip(restoreButton, i18n("mods.restore"));
|
||||
|
||||
revealButton.getStyleClass().add("toggle-icon4");
|
||||
revealButton.setGraphic(SVG.FOLDER.createIcon());
|
||||
|
||||
infoButton.getStyleClass().add("toggle-icon4");
|
||||
infoButton.setGraphic(SVG.INFO.createIcon());
|
||||
|
||||
container.getChildren().setAll(checkBox, imageView, content, restoreButton, revealButton, infoButton);
|
||||
container.getChildren().setAll(checkBox, imageContainer, content, restoreButton, revealButton, infoButton);
|
||||
|
||||
StackPane.setMargin(container, new Insets(8));
|
||||
getContainer().getChildren().setAll(container);
|
||||
@@ -616,7 +603,7 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
|
||||
ModLoaderType modLoaderType = modInfo.getModLoaderType();
|
||||
|
||||
dataItem.loadIcon(imageView, new WeakReference<>(this.itemProperty()));
|
||||
dataItem.loadIcon(imageContainer, new WeakReference<>(this.itemProperty()));
|
||||
|
||||
String displayName = modInfo.getName();
|
||||
if (modTranslations != null && I18n.isUseChinese()) {
|
||||
|
||||
@@ -8,7 +8,6 @@ import javafx.scene.Node;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
@@ -19,10 +18,7 @@ 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.construct.MDListCell;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
@@ -181,10 +177,10 @@ public final class ResourcepackListPage extends ListPageBase<ResourcepackListPag
|
||||
}
|
||||
|
||||
private static final class ResourcepackListCell extends MDListCell<ResourcepackInfoObject> {
|
||||
private final ImageView imageView = new ImageView();
|
||||
private final ImageContainer imageView = new ImageContainer(32);
|
||||
private final TwoLineListItem content = new TwoLineListItem();
|
||||
private final JFXButton btnReveal = new JFXButton();
|
||||
private final JFXButton btnDelete = new JFXButton();
|
||||
private final JFXButton btnReveal = FXUtils.newToggleButton4(SVG.FOLDER_OPEN);
|
||||
private final JFXButton btnDelete = FXUtils.newToggleButton4(SVG.DELETE_FOREVER);
|
||||
private final ResourcepackListPage page;
|
||||
|
||||
public ResourcepackListCell(JFXListView<ResourcepackInfoObject> listView, ResourcepackListPage page) {
|
||||
@@ -198,7 +194,6 @@ public final class ResourcepackListPage extends ListPageBase<ResourcepackListPag
|
||||
|
||||
HBox left = new HBox(8);
|
||||
left.setAlignment(Pos.CENTER);
|
||||
FXUtils.limitSize(imageView, 32, 32);
|
||||
left.getChildren().add(imageView);
|
||||
left.setPadding(new Insets(0, 8, 0, 0));
|
||||
FXUtils.setLimitWidth(left, 48);
|
||||
@@ -207,12 +202,6 @@ public final class ResourcepackListPage extends ListPageBase<ResourcepackListPag
|
||||
HBox.setHgrow(content, Priority.ALWAYS);
|
||||
root.setCenter(content);
|
||||
|
||||
btnReveal.getStyleClass().add("toggle-icon4");
|
||||
btnReveal.setGraphic(SVG.FOLDER_OPEN.createIcon());
|
||||
|
||||
btnDelete.getStyleClass().add("toggle-icon4");
|
||||
btnDelete.setGraphic(SVG.DELETE_FOREVER.createIcon());
|
||||
|
||||
HBox right = new HBox(8);
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
right.getChildren().setAll(btnReveal, btnDelete);
|
||||
|
||||
@@ -25,7 +25,6 @@ import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.image.PixelWriter;
|
||||
import javafx.scene.image.WritableImage;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
@@ -442,9 +441,7 @@ public final class SchematicsPage extends ListPageBase<SchematicsPage.Item> impl
|
||||
if (image == null) {
|
||||
return super.getIcon(size);
|
||||
} else {
|
||||
ImageView imageView = new ImageView();
|
||||
imageView.setFitHeight(size);
|
||||
imageView.setFitWidth(size);
|
||||
var imageView = new ImageContainer(size);
|
||||
imageView.setImage(image);
|
||||
return imageView;
|
||||
}
|
||||
@@ -548,7 +545,7 @@ public final class SchematicsPage extends ListPageBase<SchematicsPage.Item> impl
|
||||
private final TwoLineListItem center;
|
||||
private final HBox right;
|
||||
|
||||
private final ImageView iconImageView;
|
||||
private final ImageContainer iconImageView;
|
||||
private final SVGContainer iconSVGView;
|
||||
|
||||
private final Tooltip tooltip = new Tooltip();
|
||||
@@ -562,9 +559,7 @@ public final class SchematicsPage extends ListPageBase<SchematicsPage.Item> impl
|
||||
this.left = new StackPane();
|
||||
left.setPadding(new Insets(0, 8, 0, 0));
|
||||
|
||||
this.iconImageView = new ImageView();
|
||||
FXUtils.limitSize(iconImageView, 32, 32);
|
||||
|
||||
this.iconImageView = new ImageContainer(32);
|
||||
this.iconSVGView = new SVGContainer(32);
|
||||
|
||||
BorderPane.setAlignment(left, Pos.CENTER);
|
||||
@@ -580,19 +575,15 @@ public final class SchematicsPage extends ListPageBase<SchematicsPage.Item> impl
|
||||
this.right = new HBox(8);
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
JFXButton btnReveal = new JFXButton();
|
||||
JFXButton btnReveal = FXUtils.newToggleButton4(SVG.FOLDER_OPEN);
|
||||
FXUtils.installFastTooltip(btnReveal, i18n("reveal.in_file_manager"));
|
||||
btnReveal.getStyleClass().add("toggle-icon4");
|
||||
btnReveal.setGraphic(SVG.FOLDER_OPEN.createIcon());
|
||||
btnReveal.setOnAction(event -> {
|
||||
Item item = getItem();
|
||||
if (item != null && !(item instanceof BackItem))
|
||||
item.onReveal();
|
||||
});
|
||||
|
||||
JFXButton btnDelete = new JFXButton();
|
||||
btnDelete.getStyleClass().add("toggle-icon4");
|
||||
btnDelete.setGraphic(SVG.DELETE_FOREVER.createIcon());
|
||||
JFXButton btnDelete = FXUtils.newToggleButton4(SVG.DELETE_FOREVER);
|
||||
btnDelete.setOnAction(event -> {
|
||||
Item item = getItem();
|
||||
if (item != null && !(item instanceof BackItem)) {
|
||||
|
||||
@@ -21,6 +21,8 @@ import com.jfoenix.controls.JFXPopup;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.event.Event;
|
||||
import javafx.event.EventType;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
@@ -61,6 +63,13 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
|
||||
private String preferredVersionName = null;
|
||||
|
||||
public static class WorkingDirChangedEvent extends Event {
|
||||
public static final EventType<WorkingDirChangedEvent> EVENT_TYPE = new EventType<>(Event.ANY, "WORKING_DIR_CHANGED");
|
||||
public WorkingDirChangedEvent() {
|
||||
super(EVENT_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
public VersionPage() {
|
||||
versionSettingsTab.setNodeSupplier(loadVersionFor(() -> new VersionSettingsPage(false)));
|
||||
installerListTab.setNodeSupplier(loadVersionFor(InstallerListPage::new));
|
||||
@@ -74,6 +83,21 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
|
||||
addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated);
|
||||
|
||||
addEventHandler(WorkingDirChangedEvent.EVENT_TYPE, event -> {
|
||||
if (this.version.get() != null) {
|
||||
if (installerListTab.isInitialized())
|
||||
installerListTab.getNode().loadVersion(getProfile(), getVersion());
|
||||
if (modListTab.isInitialized())
|
||||
modListTab.getNode().loadVersion(getProfile(), getVersion());
|
||||
if (resourcePackTab.isInitialized())
|
||||
resourcePackTab.getNode().loadVersion(getProfile(), getVersion());
|
||||
if (worldListTab.isInitialized())
|
||||
worldListTab.getNode().loadVersion(getProfile(), getVersion());
|
||||
if (schematicsTab.isInitialized())
|
||||
schematicsTab.getNode().loadVersion(getProfile(), getVersion());
|
||||
}
|
||||
});
|
||||
|
||||
listenerHolder.add(EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> checkSelectedVersion(), EventPriority.HIGHEST));
|
||||
}
|
||||
|
||||
|
||||
@@ -34,8 +34,12 @@ import javafx.scene.layout.*;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Screen;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.game.GameDirectoryType;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.ProcessPriority;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.java.JavaManager;
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
import org.jackhuang.hmcl.setting.*;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
@@ -48,7 +52,6 @@ import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
import org.jackhuang.hmcl.util.javafx.PropertyUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.SystemInfo;
|
||||
import org.jackhuang.hmcl.util.platform.hardware.PhysicalMemoryStatus;
|
||||
@@ -99,6 +102,10 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
private final InvalidationListener usesGlobalListener;
|
||||
private final ChangeListener<Boolean> specificSettingsListener;
|
||||
private final InvalidationListener javaListener = any -> initJavaSubtitle();
|
||||
|
||||
private final ChangeListener<GameDirectoryType> gameDirTypeListener = (ob, o, n) -> fireWorkingDirChanged();
|
||||
private final ChangeListener<String> gameDirListener = (ob, o, n) -> fireWorkingDirChanged();
|
||||
|
||||
private boolean updatingJavaSetting = false;
|
||||
private boolean updatingSelectedJava = false;
|
||||
|
||||
@@ -455,13 +462,20 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
else
|
||||
profile.getRepository().globalizeVersionSetting(versionId);
|
||||
|
||||
Platform.runLater(() -> loadVersion(profile, versionId));
|
||||
FXUtils.runInFX(() -> {
|
||||
loadVersion(profile, versionId);
|
||||
fireWorkingDirChanged();
|
||||
});
|
||||
};
|
||||
|
||||
addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onDecoratorPageNavigating);
|
||||
|
||||
componentList.disableProperty().bind(enableSpecificSettings.not());
|
||||
}
|
||||
|
||||
private void fireWorkingDirChanged() {
|
||||
FXUtils.runInFX(() -> fireEvent(new VersionPage.WorkingDirChangedEvent()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadVersion(Profile profile, String versionId) {
|
||||
@@ -513,6 +527,9 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
lastVersionSetting.defaultJavaPathPropertyProperty().removeListener(javaListener);
|
||||
lastVersionSetting.javaVersionProperty().removeListener(javaListener);
|
||||
|
||||
lastVersionSetting.gameDirTypeProperty().removeListener(gameDirTypeListener);
|
||||
lastVersionSetting.gameDirProperty().removeListener(gameDirListener);
|
||||
|
||||
gameDirItem.selectedDataProperty().unbindBidirectional(lastVersionSetting.gameDirTypeProperty());
|
||||
gameDirSublist.subtitleProperty().unbind();
|
||||
|
||||
@@ -589,6 +606,9 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
gameDirSublist.subtitleProperty().bind(Bindings.createStringBinding(() -> profile.getRepository().getRunDirectory(versionId).toAbsolutePath().normalize().toString(),
|
||||
versionSetting.gameDirProperty(), versionSetting.gameDirTypeProperty()));
|
||||
|
||||
versionSetting.gameDirTypeProperty().addListener(gameDirTypeListener);
|
||||
versionSetting.gameDirProperty().addListener(gameDirListener);
|
||||
|
||||
lastVersionSetting = versionSetting;
|
||||
|
||||
initJavaSubtitle();
|
||||
@@ -727,7 +747,6 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
}
|
||||
|
||||
iconPickerItem.setImage(profile.getRepository().getVersionIconImage(versionId));
|
||||
FXUtils.limitSize(iconPickerItem.getImageView(), 32, 32);
|
||||
}
|
||||
|
||||
private static List<String> getSupportedResolutions() {
|
||||
|
||||
@@ -22,6 +22,7 @@ import javafx.application.Platform;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
@@ -42,13 +43,13 @@ import org.jackhuang.hmcl.ui.export.ExportWizardProvider;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
@@ -72,11 +73,11 @@ public final class Versions {
|
||||
}
|
||||
}
|
||||
|
||||
public static void downloadModpackImpl(Profile profile, String version, RemoteMod mod, RemoteMod.Version file) {
|
||||
public static void downloadModpackImpl(DownloadProvider downloadProvider, Profile profile, String version, RemoteMod mod, RemoteMod.Version file) {
|
||||
Path modpack;
|
||||
URI downloadURL;
|
||||
List<URI> downloadURLs;
|
||||
try {
|
||||
downloadURL = NetworkUtils.toURI(file.getFile().getUrl());
|
||||
downloadURLs = downloadProvider.injectURLWithCandidates(file.getFile().getUrl());
|
||||
modpack = Files.createTempFile("modpack", ".zip");
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
Controllers.dialog(
|
||||
@@ -85,7 +86,7 @@ public final class Versions {
|
||||
return;
|
||||
}
|
||||
Controllers.taskDialog(
|
||||
new FileDownloadTask(downloadURL, modpack)
|
||||
new FileDownloadTask(downloadURLs, modpack)
|
||||
.whenComplete(Schedulers.javafx(), e -> {
|
||||
if (e == null) {
|
||||
ModpackInstallWizardProvider installWizardProvider;
|
||||
|
||||
@@ -26,7 +26,6 @@ import javafx.scene.Node;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
@@ -35,6 +34,7 @@ import org.jackhuang.hmcl.game.WorldLockedException;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.*;
|
||||
import org.jackhuang.hmcl.ui.construct.ImageContainer;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
@@ -232,9 +232,8 @@ public final class WorldBackupsPage extends ListPageBase<WorldBackupsPage.Backup
|
||||
root.setLeft(left);
|
||||
left.setPadding(new Insets(0, 8, 0, 0));
|
||||
|
||||
ImageView imageView = new ImageView();
|
||||
var imageView = new ImageContainer(32);
|
||||
left.getChildren().add(imageView);
|
||||
FXUtils.limitSize(imageView, 32, 32);
|
||||
imageView.setImage(world.getIcon() == null ? FXUtils.newBuiltinImage("/assets/img/unknown_server.png") : world.getIcon());
|
||||
}
|
||||
|
||||
@@ -255,18 +254,14 @@ public final class WorldBackupsPage extends ListPageBase<WorldBackupsPage.Backup
|
||||
root.setRight(right);
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
JFXButton btnReveal = new JFXButton();
|
||||
JFXButton btnReveal = FXUtils.newToggleButton4(SVG.FOLDER_OPEN);
|
||||
right.getChildren().add(btnReveal);
|
||||
FXUtils.installFastTooltip(btnReveal, i18n("reveal.in_file_manager"));
|
||||
btnReveal.getStyleClass().add("toggle-icon4");
|
||||
btnReveal.setGraphic(SVG.FOLDER_OPEN.createIcon());
|
||||
btnReveal.setOnAction(event -> skinnable.onReveal());
|
||||
|
||||
JFXButton btnDelete = new JFXButton();
|
||||
JFXButton btnDelete = FXUtils.newToggleButton4(SVG.DELETE_FOREVER);
|
||||
right.getChildren().add(btnDelete);
|
||||
FXUtils.installFastTooltip(btnDelete, i18n("world.backup.delete"));
|
||||
btnDelete.getStyleClass().add("toggle-icon4");
|
||||
btnDelete.setGraphic(SVG.DELETE.createIcon());
|
||||
btnDelete.setOnAction(event -> Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), skinnable::onDelete, null));
|
||||
}
|
||||
|
||||
|
||||
@@ -30,14 +30,11 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.effect.BoxBlur;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.game.World;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
@@ -67,9 +64,10 @@ public final class WorldInfoPage extends SpinnerPane implements WorldManagePage.
|
||||
private final WorldManagePage worldManagePage;
|
||||
private boolean isReadOnly;
|
||||
private final World world;
|
||||
private CompoundTag levelDat;
|
||||
private CompoundTag levelData;
|
||||
private CompoundTag playerData;
|
||||
|
||||
ImageView iconImageView = new ImageView();
|
||||
private final ImageContainer iconImageView = new ImageContainer(32);
|
||||
|
||||
public WorldInfoPage(WorldManagePage worldManagePage) {
|
||||
this.worldManagePage = worldManagePage;
|
||||
@@ -77,15 +75,9 @@ public final class WorldInfoPage extends SpinnerPane implements WorldManagePage.
|
||||
refresh();
|
||||
}
|
||||
|
||||
private CompoundTag loadWorldInfo() throws IOException {
|
||||
if (!Files.isDirectory(world.getFile()))
|
||||
throw new IOException("Not a valid world directory");
|
||||
|
||||
return world.getLevelData();
|
||||
}
|
||||
|
||||
private void updateControls() {
|
||||
CompoundTag dataTag = levelDat.get("Data");
|
||||
CompoundTag dataTag = levelData.get("Data");
|
||||
CompoundTag playerTag = playerData;
|
||||
|
||||
ScrollPane scrollPane = new ScrollPane();
|
||||
scrollPane.setFitToHeight(true);
|
||||
@@ -137,14 +129,12 @@ public final class WorldInfoPage extends SpinnerPane implements WorldManagePage.
|
||||
iconPane.setTitle(i18n("world.icon"));
|
||||
|
||||
{
|
||||
FXUtils.limitSize(iconImageView, 32, 32);
|
||||
iconImageView.setImage(world.getIcon() == null ? FXUtils.newBuiltinImage("/assets/img/unknown_server.png") : world.getIcon());
|
||||
}
|
||||
|
||||
JFXButton editIconButton = new JFXButton();
|
||||
JFXButton resetIconButton = new JFXButton();
|
||||
JFXButton editIconButton = FXUtils.newToggleButton4(SVG.EDIT, 20);
|
||||
JFXButton resetIconButton = FXUtils.newToggleButton4(SVG.RESTORE, 20);
|
||||
{
|
||||
editIconButton.setGraphic(SVG.EDIT.createIcon(20));
|
||||
editIconButton.setDisable(isReadOnly);
|
||||
editIconButton.setOnAction(event -> Controllers.confirm(
|
||||
I18n.i18n("world.icon.change.tip"),
|
||||
@@ -154,13 +144,10 @@ public final class WorldInfoPage extends SpinnerPane implements WorldManagePage.
|
||||
null
|
||||
));
|
||||
FXUtils.installFastTooltip(editIconButton, i18n("button.edit"));
|
||||
editIconButton.getStyleClass().add("toggle-icon4");
|
||||
|
||||
resetIconButton.setGraphic(SVG.RESTORE.createIcon(20));
|
||||
resetIconButton.setDisable(isReadOnly);
|
||||
resetIconButton.setOnAction(event -> this.clearWorldIcon());
|
||||
FXUtils.installFastTooltip(resetIconButton, i18n("button.reset"));
|
||||
resetIconButton.getStyleClass().add("toggle-icon4");
|
||||
}
|
||||
|
||||
HBox hBox = new HBox(8);
|
||||
@@ -211,12 +198,14 @@ public final class WorldInfoPage extends SpinnerPane implements WorldManagePage.
|
||||
worldSpawnPoint.setTitle(i18n("world.info.spawn"));
|
||||
|
||||
String value;
|
||||
// Valid after 1.21.9-pre1
|
||||
if (dataTag.get("spawn") instanceof CompoundTag spawnTag && spawnTag.get("pos") instanceof IntArrayTag posTag) {
|
||||
value = Dimension.of(spawnTag.get("dimension") instanceof StringTag dimensionTag
|
||||
? dimensionTag
|
||||
: new StringTag("SpawnDimension", "minecraft:overworld"))
|
||||
.formatPosition(posTag);
|
||||
} else if (dataTag.get("SpawnX") instanceof IntTag intX
|
||||
value = (spawnTag.get("dimension") instanceof StringTag dimensionTag)
|
||||
? Dimension.of(dimensionTag).formatPosition(posTag)
|
||||
: Dimension.OVERWORLD.formatPosition(posTag);
|
||||
}
|
||||
// Valid before 1.21.9-pre1
|
||||
else if (dataTag.get("SpawnX") instanceof IntTag intX
|
||||
&& dataTag.get("SpawnY") instanceof IntTag intY
|
||||
&& dataTag.get("SpawnZ") instanceof IntTag intZ) {
|
||||
value = Dimension.OVERWORLD.formatPosition(intX.getValue(), intY.getValue(), intZ.getValue());
|
||||
@@ -254,34 +243,51 @@ public final class WorldInfoPage extends SpinnerPane implements WorldManagePage.
|
||||
{
|
||||
generateFeaturesButton.setTitle(i18n("world.info.generate_features"));
|
||||
generateFeaturesButton.setDisable(isReadOnly);
|
||||
|
||||
// generate_features was valid after 20w20a and MapFeatures was before that
|
||||
if (dataTag.get("WorldGenSettings") instanceof CompoundTag worldGenSettings) {
|
||||
bindTagAndToggleButton(worldGenSettings.get("generate_features"), generateFeaturesButton);
|
||||
// Valid before (1.16)20w20a
|
||||
if (dataTag.get("MapFeatures") instanceof ByteTag generateFeaturesTag) {
|
||||
bindTagAndToggleButton(generateFeaturesTag, generateFeaturesButton);
|
||||
}
|
||||
// Valid after (1.16)20w20a
|
||||
else if (world.getNormalizedWorldGenSettingsData() != null) {
|
||||
CompoundTag unifiedWorldGenSettingsDataTag = world.getNormalizedWorldGenSettingsData();
|
||||
Tag tag = unifiedWorldGenSettingsDataTag.get("generate_features"); // Valid between (1.16)20w20a and 26.1-snapshot-6
|
||||
if (tag == null)
|
||||
tag = unifiedWorldGenSettingsDataTag.get("generate_structures"); // Valid after 26.1-snapshot-6
|
||||
bindTagAndToggleButton(tag, generateFeaturesButton);
|
||||
} else {
|
||||
bindTagAndToggleButton(dataTag.get("MapFeatures"), generateFeaturesButton);
|
||||
generateFeaturesButton.setDisable(true);
|
||||
}
|
||||
}
|
||||
|
||||
var difficultyButton = new LineSelectButton<Difficulty>();
|
||||
{
|
||||
difficultyButton.setTitle(i18n("world.info.difficulty"));
|
||||
difficultyButton.setDisable(worldManagePage.isReadOnly());
|
||||
difficultyButton.setDisable(isReadOnly);
|
||||
difficultyButton.setItems(Difficulty.items);
|
||||
|
||||
if (dataTag.get("Difficulty") instanceof ByteTag difficultyTag) {
|
||||
Difficulty difficulty = Difficulty.of(difficultyTag.getValue());
|
||||
if (difficulty != null) {
|
||||
difficultyButton.setValue(difficulty);
|
||||
difficultyButton.valueProperty().addListener((o, oldValue, newValue) -> {
|
||||
if (newValue != null) {
|
||||
difficultyTag.setValue((byte) newValue.ordinal());
|
||||
saveLevelDat();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
difficultyButton.setDisable(true);
|
||||
}
|
||||
Difficulty difficulty;
|
||||
// Valid before 26.1-snapshot-6
|
||||
if (dataTag.get("Difficulty") instanceof ByteTag difficultyTag
|
||||
&& (difficulty = Difficulty.of(difficultyTag.getValue())) != null) {
|
||||
difficultyButton.setValue(difficulty);
|
||||
difficultyButton.valueProperty().addListener((o, oldValue, newValue) -> {
|
||||
if (newValue != null) {
|
||||
difficultyTag.setValue((byte) newValue.ordinal());
|
||||
saveWorldData();
|
||||
}
|
||||
});
|
||||
}
|
||||
// Valid after 26.1-snapshot-6
|
||||
else if (dataTag.get("difficulty_settings") instanceof CompoundTag difficultySettingTag
|
||||
&& difficultySettingTag.get("difficulty") instanceof StringTag difficultyTag
|
||||
&& (difficulty = Difficulty.of(difficultyTag.getValue())) != null) {
|
||||
difficultyButton.setValue(difficulty);
|
||||
difficultyButton.valueProperty().addListener((o, oldValue, newValue) -> {
|
||||
if (newValue != null) {
|
||||
difficultyTag.setValue(newValue.getTagStringValue());
|
||||
saveWorldData();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
difficultyButton.setDisable(true);
|
||||
}
|
||||
@@ -291,8 +297,17 @@ public final class WorldInfoPage extends SpinnerPane implements WorldManagePage.
|
||||
{
|
||||
difficultyLockPane.setTitle(i18n("world.info.difficulty_lock"));
|
||||
difficultyLockPane.setDisable(isReadOnly);
|
||||
|
||||
bindTagAndToggleButton(dataTag.get("DifficultyLocked"), difficultyLockPane);
|
||||
// Valid before 26.1-snapshot-6
|
||||
if (dataTag.get("DifficultyLocked") instanceof ByteTag difficultyLockedTag) {
|
||||
bindTagAndToggleButton(difficultyLockedTag, difficultyLockPane);
|
||||
}
|
||||
// Valid after 26.1-snapshot-6
|
||||
else if (dataTag.get("difficulty_settings") instanceof CompoundTag difficultySettingTag
|
||||
&& difficultySettingTag.get("locked") instanceof ByteTag lockedTag) {
|
||||
bindTagAndToggleButton(lockedTag, difficultyLockPane);
|
||||
} else {
|
||||
difficultyLockPane.setDisable(true);
|
||||
}
|
||||
}
|
||||
|
||||
worldInfo.getContent().setAll(
|
||||
@@ -302,7 +317,7 @@ public final class WorldInfoPage extends SpinnerPane implements WorldManagePage.
|
||||
rootPane.getChildren().addAll(ComponentList.createComponentListTitle(i18n("world.info")), worldInfo);
|
||||
}
|
||||
|
||||
if (dataTag.get("Player") instanceof CompoundTag playerTag) {
|
||||
if (playerTag != null) {
|
||||
ComponentList playerInfo = new ComponentList();
|
||||
|
||||
var locationPane = new LineTextPane();
|
||||
@@ -349,8 +364,14 @@ public final class WorldInfoPage extends SpinnerPane implements WorldManagePage.
|
||||
playerGameTypePane.setDisable(worldManagePage.isReadOnly());
|
||||
playerGameTypePane.setItems(GameType.items);
|
||||
|
||||
if (playerTag.get("playerGameType") instanceof IntTag playerGameTypeTag
|
||||
&& dataTag.get("hardcore") instanceof ByteTag hardcoreTag) {
|
||||
// Valid before 26.1-snapshot-6
|
||||
Tag rawHardcoreTag = dataTag.get("hardcore");
|
||||
// Valid after 26.1-snapshot-6
|
||||
if (rawHardcoreTag == null && dataTag.get("difficulty_settings") instanceof CompoundTag difficultySettingsTag) {
|
||||
rawHardcoreTag = difficultySettingsTag.get("hardcore");
|
||||
}
|
||||
|
||||
if (playerTag.get("playerGameType") instanceof IntTag playerGameTypeTag && rawHardcoreTag instanceof ByteTag hardcoreTag) {
|
||||
boolean isHardcore = hardcoreTag.getValue() == 1;
|
||||
GameType gameType = GameType.of(playerGameTypeTag.getValue(), isHardcore);
|
||||
if (gameType != null) {
|
||||
@@ -364,7 +385,7 @@ public final class WorldInfoPage extends SpinnerPane implements WorldManagePage.
|
||||
playerGameTypeTag.setValue(newValue.ordinal());
|
||||
hardcoreTag.setValue((byte) 0);
|
||||
}
|
||||
saveLevelDat();
|
||||
saveWorldData();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@@ -434,10 +455,10 @@ public final class WorldInfoPage extends SpinnerPane implements WorldManagePage.
|
||||
toggleButton.selectedProperty().addListener((o, oldValue, newValue) -> {
|
||||
try {
|
||||
byteTag.setValue((byte) (newValue ? 1 : 0));
|
||||
saveLevelDat();
|
||||
saveWorldData();
|
||||
} catch (Exception e) {
|
||||
toggleButton.setSelected(oldValue);
|
||||
LOG.warning("Exception happened when saving level.dat", e);
|
||||
LOG.warning("Exception happened when saving world info", e);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@@ -457,11 +478,11 @@ public final class WorldInfoPage extends SpinnerPane implements WorldManagePage.
|
||||
Integer integer = Lang.toIntOrNull(newValue);
|
||||
if (integer != null) {
|
||||
intTag.setValue(integer);
|
||||
saveLevelDat();
|
||||
saveWorldData();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
jfxTextField.setText(oldValue);
|
||||
LOG.warning("Exception happened when saving level.dat", e);
|
||||
LOG.warning("Exception happened when saving world data", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -478,11 +499,11 @@ public final class WorldInfoPage extends SpinnerPane implements WorldManagePage.
|
||||
Float floatValue = Lang.toFloatOrNull(newValue);
|
||||
if (floatValue != null) {
|
||||
floatTag.setValue(floatValue);
|
||||
saveLevelDat();
|
||||
saveWorldData();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
jfxTextField.setText(oldValue);
|
||||
LOG.warning("Exception happened when saving level.dat", e);
|
||||
LOG.warning("Exception happened when saving world data", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -490,30 +511,27 @@ public final class WorldInfoPage extends SpinnerPane implements WorldManagePage.
|
||||
jfxTextField.setValidators(new DoubleValidator(i18n("input.number"), true));
|
||||
}
|
||||
|
||||
private void saveLevelDat() {
|
||||
LOG.info("Saving level.dat of world " + world.getWorldName());
|
||||
private void saveWorldData() {
|
||||
LOG.info("Saving data of world " + world.getWorldName());
|
||||
try {
|
||||
this.world.writeLevelDat(levelDat);
|
||||
this.world.writeWorldData();
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to save level.dat of world " + world.getWorldName(), e);
|
||||
LOG.warning("Failed to save world data", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh() {
|
||||
this.isReadOnly = worldManagePage.isReadOnly();
|
||||
this.setLoading(true);
|
||||
Task.supplyAsync(this::loadWorldInfo)
|
||||
.whenComplete(Schedulers.javafx(), ((result, exception) -> {
|
||||
if (exception == null) {
|
||||
this.levelDat = result;
|
||||
updateControls();
|
||||
setLoading(false);
|
||||
} else {
|
||||
LOG.warning("Failed to load level.dat", exception);
|
||||
setFailedReason(i18n("world.info.failed"));
|
||||
}
|
||||
})).start();
|
||||
setFailedReason(null);
|
||||
try {
|
||||
this.isReadOnly = worldManagePage.isReadOnly();
|
||||
this.levelData = world.getLevelData();
|
||||
this.playerData = world.getPlayerData();
|
||||
updateControls();
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to refresh world info", e);
|
||||
setFailedReason(i18n("world.info.failed"));
|
||||
}
|
||||
}
|
||||
|
||||
private record Dimension(String name) {
|
||||
@@ -594,6 +612,15 @@ public final class WorldInfoPage extends SpinnerPane implements WorldManagePage.
|
||||
return (d >= 0 && d < items.size()) ? items.get(d) : null;
|
||||
}
|
||||
|
||||
static Difficulty of(String name) {
|
||||
for (Difficulty d : items) if (d.name().toLowerCase(Locale.ROOT).equals(name)) return d;
|
||||
return null;
|
||||
}
|
||||
|
||||
String getTagStringValue() {
|
||||
return this.name().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return i18n("world.info.difficulty." + name().toLowerCase(Locale.ROOT));
|
||||
@@ -659,7 +686,7 @@ public final class WorldInfoPage extends SpinnerPane implements WorldManagePage.
|
||||
Files.deleteIfExists(output);
|
||||
iconImageView.setImage(FXUtils.newBuiltinImage("/assets/img/unknown_server.png"));
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to delete world icon " + e.getMessage());
|
||||
LOG.warning("Failed to delete world icon ", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,6 @@ import javafx.scene.Node;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
@@ -243,7 +242,7 @@ public final class WorldListPage extends ListPageBase<World> implements VersionP
|
||||
private final WorldListPage page;
|
||||
|
||||
private final RipplerContainer graphic;
|
||||
private final ImageView imageView;
|
||||
private final ImageContainer imageView;
|
||||
private final Tooltip leftTooltip;
|
||||
private final TwoLineListItem content;
|
||||
private final JFXButton btnLaunch;
|
||||
@@ -262,9 +261,8 @@ public final class WorldListPage extends ListPageBase<World> implements VersionP
|
||||
root.setLeft(left);
|
||||
left.setPadding(new Insets(0, 8, 0, 0));
|
||||
|
||||
this.imageView = new ImageView();
|
||||
this.imageView = new ImageContainer(32);
|
||||
left.getChildren().add(imageView);
|
||||
FXUtils.limitSize(imageView, 32, 32);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -278,12 +276,10 @@ public final class WorldListPage extends ListPageBase<World> implements VersionP
|
||||
root.setRight(right);
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
btnLaunch = new JFXButton();
|
||||
btnLaunch = FXUtils.newToggleButton4(SVG.ROCKET_LAUNCH);
|
||||
btnLaunch.visibleProperty().bind(page.supportQuickPlayProperty());
|
||||
btnLaunch.managedProperty().bind(btnLaunch.visibleProperty());
|
||||
right.getChildren().add(btnLaunch);
|
||||
btnLaunch.getStyleClass().add("toggle-icon4");
|
||||
btnLaunch.setGraphic(SVG.ROCKET_LAUNCH.createIcon());
|
||||
FXUtils.installFastTooltip(btnLaunch, i18n("version.launch"));
|
||||
btnLaunch.setOnAction(event -> {
|
||||
World world = getItem();
|
||||
@@ -291,10 +287,8 @@ public final class WorldListPage extends ListPageBase<World> implements VersionP
|
||||
page.launch(world);
|
||||
});
|
||||
|
||||
JFXButton btnMore = new JFXButton();
|
||||
JFXButton btnMore = FXUtils.newToggleButton4(SVG.MORE_VERT);
|
||||
right.getChildren().add(btnMore);
|
||||
btnMore.getStyleClass().add("toggle-icon4");
|
||||
btnMore.setGraphic(SVG.MORE_VERT.createIcon());
|
||||
btnMore.setOnAction(event -> {
|
||||
World world = getItem();
|
||||
if (world != null)
|
||||
@@ -388,7 +382,7 @@ public final class WorldListPage extends ListPageBase<World> implements VersionP
|
||||
IconedMenuItem exportMenuItem = new IconedMenuItem(SVG.OUTPUT, i18n("world.export"), () -> page.export(world), popup);
|
||||
exportMenuItem.setDisable(worldLocked);
|
||||
|
||||
IconedMenuItem deleteMenuItem = new IconedMenuItem(SVG.DELETE, i18n("world.delete"), () -> page.delete(world), popup);
|
||||
IconedMenuItem deleteMenuItem = new IconedMenuItem(SVG.DELETE_FOREVER, i18n("world.delete"), () -> page.delete(world), popup);
|
||||
deleteMenuItem.setDisable(worldLocked);
|
||||
|
||||
IconedMenuItem duplicateMenuItem = new IconedMenuItem(SVG.CONTENT_COPY, i18n("world.duplicate"), () -> page.copy(world), popup);
|
||||
|
||||
@@ -78,9 +78,9 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco
|
||||
updateSessionLockChannel();
|
||||
|
||||
try {
|
||||
this.world.reloadLevelDat();
|
||||
this.world.reloadWorldData();
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Can not load world level.dat of world: " + this.world.getFile(), e);
|
||||
LOG.warning("Can not load world data of world: " + this.world.getFile(), e);
|
||||
this.addEventHandler(Navigator.NavigationEvent.NAVIGATED, event -> closePageForLoadingFail());
|
||||
}
|
||||
|
||||
@@ -106,9 +106,9 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco
|
||||
public void refresh() {
|
||||
updateSessionLockChannel();
|
||||
try {
|
||||
world.reloadLevelDat();
|
||||
world.reloadWorldData();
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Can not load world level.dat of world: " + world.getFile(), e);
|
||||
LOG.warning("Can not load world data of world: " + world.getFile(), e);
|
||||
closePageForLoadingFail();
|
||||
return;
|
||||
}
|
||||
@@ -273,7 +273,7 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco
|
||||
|
||||
managePopupMenu.getContent().addAll(
|
||||
new IconedMenuItem(SVG.OUTPUT, i18n("world.export"), () -> WorldManageUIUtils.export(getSkinnable().world, getSkinnable().sessionLockChannel), managePopup),
|
||||
new IconedMenuItem(SVG.DELETE, i18n("world.delete"), () -> WorldManageUIUtils.delete(getSkinnable().world, () -> getSkinnable().fireEvent(new PageCloseEvent()), getSkinnable().sessionLockChannel), managePopup),
|
||||
new IconedMenuItem(SVG.DELETE_FOREVER, i18n("world.delete"), () -> WorldManageUIUtils.delete(getSkinnable().world, () -> getSkinnable().fireEvent(new PageCloseEvent()), getSkinnable().sessionLockChannel), managePopup),
|
||||
new IconedMenuItem(SVG.CONTENT_COPY, i18n("world.duplicate"), () -> WorldManageUIUtils.copyWorld(getSkinnable().world, null), managePopup)
|
||||
);
|
||||
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.wizard;
|
||||
|
||||
public enum WizardNavigationResult {
|
||||
PROCEED {
|
||||
@Override
|
||||
public boolean getDeferredComputation() {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
DENY {
|
||||
@Override
|
||||
public boolean getDeferredComputation() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
public abstract boolean getDeferredComputation();
|
||||
}
|
||||
@@ -37,6 +37,7 @@ import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@@ -174,15 +175,25 @@ public final class UpdateHandler {
|
||||
startJava(updateTo, "--apply-to", self.toString());
|
||||
}
|
||||
|
||||
private static void startJava(Path jar, String... appArgs) throws IOException {
|
||||
public static void startJava(Path jar, String... appArgs) throws IOException {
|
||||
List<String> commandline = new ArrayList<>();
|
||||
commandline.add(JavaRuntime.getDefault().getBinary().toString());
|
||||
for (Map.Entry<Object, Object> entry : System.getProperties().entrySet()) {
|
||||
Object key = entry.getKey();
|
||||
if (key instanceof String && ((String) key).startsWith("hmcl.")) {
|
||||
commandline.add("-D" + key + "=" + entry.getValue());
|
||||
|
||||
try {
|
||||
for (String inputArgument : ManagementFactory.getRuntimeMXBean().getInputArguments()) {
|
||||
if (inputArgument.startsWith("-D") || inputArgument.startsWith("-X")) {
|
||||
commandline.add(inputArgument);
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
// ManagementFactory not available
|
||||
for (Map.Entry<Object, Object> entry : System.getProperties().entrySet()) {
|
||||
if (entry.getKey() instanceof String key && key.startsWith("hmcl.")) {
|
||||
commandline.add("-D" + key + "=" + entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
commandline.add("-jar");
|
||||
commandline.add(jar.toAbsolutePath().toString());
|
||||
commandline.addAll(Arrays.asList(appArgs));
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import org.jackhuang.hmcl.util.i18n.LocaleUtils;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.util.List;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
/// April Fools' Day utilities.
|
||||
///
|
||||
/// This class provides methods to check if it is April Fools' Day or near April Fools' Day.
|
||||
/// It also provides a method to check if April Fools is enabled.
|
||||
///
|
||||
/// @author Glavo
|
||||
public final class AprilFools {
|
||||
|
||||
private static final boolean ENABLED;
|
||||
private static final boolean SHOW_APRIL_FOOLS_SETTINGS;
|
||||
|
||||
static {
|
||||
var date = LocalDate.now();
|
||||
|
||||
// Some countries/regions may oppose April Fools' Day for various reasons.
|
||||
// Therefore, we use a regional whitelist to avoid risks.
|
||||
// Currently, we have only listed a limited set of countries/regions for testing.
|
||||
// We will investigate more countries/regions in the future to expand this list.
|
||||
boolean supportedRegion = List.of(
|
||||
"CN", "TW", "HK", "MO", "JP", "KR", "VN", "SG", "MY",
|
||||
"ES", "DE", "FR", "GB", "RU", "UA", "US"
|
||||
).contains(LocaleUtils.SYSTEM_DEFAULT.getCountry());
|
||||
|
||||
boolean aprilFoolsMode;
|
||||
String value = System.getProperty("hmcl.april_fools", System.getenv("HMCL_APRIL_FOOLS"));
|
||||
if ("true".equalsIgnoreCase(value))
|
||||
aprilFoolsMode = true;
|
||||
else if ("false".equalsIgnoreCase(value) || !supportedRegion)
|
||||
aprilFoolsMode = false;
|
||||
else
|
||||
aprilFoolsMode = date.getMonth() == Month.APRIL && date.getDayOfMonth() == 1;
|
||||
|
||||
ENABLED = aprilFoolsMode && !config().isDisableAprilFools();
|
||||
SHOW_APRIL_FOOLS_SETTINGS = aprilFoolsMode || supportedRegion && date.getMonth() == Month.MARCH && date.getDayOfMonth() > 30;
|
||||
}
|
||||
|
||||
/// Whether April Fools settings should be shown.
|
||||
///
|
||||
/// This method returns true if April Fools settings should be shown.
|
||||
public static boolean isShowAprilFoolsSettings() {
|
||||
return SHOW_APRIL_FOOLS_SETTINGS;
|
||||
}
|
||||
|
||||
/// Whether April Fools is enabled.
|
||||
///
|
||||
/// This method returns true if April Fools is enabled.
|
||||
public static boolean isEnabled() {
|
||||
return ENABLED;
|
||||
}
|
||||
|
||||
private AprilFools() {
|
||||
}
|
||||
}
|
||||
@@ -31,21 +31,27 @@ public final class ChunkBaseApp {
|
||||
private static final GameVersionNumber MIN_END_CITY_VERSION = GameVersionNumber.asGameVersion("1.13");
|
||||
|
||||
private static final String[] SEED_MAP_GAME_VERSIONS = {
|
||||
"1.21.9", "1.21.6", "1.21.5", "1.21.4", "1.21.2", "1.21", "1.20",
|
||||
"1.19.3", "1.19", "1.18", "1.17", "1.16", "1.15", "1.14", "1.13",
|
||||
"1.12", "1.11", "1.10", "1.9", "1.8", "1.7"
|
||||
"26.1", "1.21.9", "1.21.6", "1.21.5", "1.21.4", "1.21.2", "1.21",
|
||||
"1.20", "1.19.3", "1.19", "1.18", "1.17", "1.16", "1.15", "1.14",
|
||||
"1.13", "1.12", "1.11", "1.10", "1.9", "1.8", "1.7"
|
||||
};
|
||||
|
||||
public static final String[] STRONGHOLD_FINDER_GAME_VERSIONS = {
|
||||
"1.20", "1.19.3", "1.19", "1.18", "1.16", "1.13", "1.9", "1.7"
|
||||
"26.1", "1.21.9", "1.21.6", "1.21.5", "1.21.4", "1.21.2", "1.21",
|
||||
"1.20", "1.19.3", "1.19", "1.18", "1.17", "1.16", "1.15", "1.14",
|
||||
"1.13", "1.12", "1.11", "1.10", "1.9", "1.8", "1.7"
|
||||
};
|
||||
|
||||
public static final String[] NETHER_FORTRESS_GAME_VERSIONS = {
|
||||
"1.18", "1.16", "1.7"
|
||||
"26.1", "1.21.9", "1.21.6", "1.21.5", "1.21.4", "1.21.2", "1.21",
|
||||
"1.20", "1.19.3", "1.19", "1.18", "1.17", "1.16", "1.15", "1.14",
|
||||
"1.13", "1.12", "1.11", "1.10", "1.9", "1.8", "1.7"
|
||||
};
|
||||
|
||||
public static final String[] END_CITY_GAME_VERSIONS = {
|
||||
"1.19", "1.13"
|
||||
"26.1", "1.21.9", "1.21.6", "1.21.5", "1.21.4", "1.21.2", "1.21",
|
||||
"1.20", "1.19.3", "1.19", "1.18", "1.17", "1.16", "1.15", "1.14",
|
||||
"1.13"
|
||||
};
|
||||
|
||||
public static boolean isSupported(@NotNull World world) {
|
||||
|
||||
@@ -20,20 +20,12 @@ package org.jackhuang.hmcl.util;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Alert.AlertType;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.countly.CrashReport;
|
||||
import org.jackhuang.hmcl.ui.CrashWindow;
|
||||
import org.jackhuang.hmcl.upgrade.IntegrityChecker;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
@@ -103,9 +95,6 @@ public final class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
if (showCrashWindow) {
|
||||
new CrashWindow(report).show();
|
||||
}
|
||||
if (!UpdateChecker.isOutdated() && IntegrityChecker.isSelfVerified()) {
|
||||
reportToServer(report);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Throwable handlingException) {
|
||||
@@ -115,22 +104,4 @@ public final class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
FileSaver.shutdown();
|
||||
LOG.shutdown();
|
||||
}
|
||||
|
||||
private void reportToServer(CrashReport crashReport) {
|
||||
Thread t = new Thread(() -> {
|
||||
HashMap<String, String> map = new HashMap<>();
|
||||
map.put("crash_report", crashReport.getDisplayText());
|
||||
map.put("version", Metadata.VERSION);
|
||||
map.put("log", LOG.getLogs());
|
||||
try {
|
||||
String response = NetworkUtils.doPost(URI.create(Metadata.PUBLISH_URL + "/hmcl/crash.php"), map);
|
||||
if (StringUtils.isNotBlank(response))
|
||||
LOG.error("Crash server response: " + response);
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Unable to post HMCL server.", ex);
|
||||
}
|
||||
});
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@@ -29,18 +30,21 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
/// @author Glavo
|
||||
public final class FileSaver extends Thread {
|
||||
|
||||
private static final Pair<Path, String> SHUTDOWN = Pair.pair(null, null);
|
||||
|
||||
private static final BlockingQueue<Pair<Path, String>> queue = new LinkedBlockingQueue<>();
|
||||
private static final BlockingQueue<Action> queue = new LinkedBlockingQueue<>();
|
||||
private static final AtomicBoolean running = new AtomicBoolean(false);
|
||||
private static final ReentrantLock runningLock = new ReentrantLock();
|
||||
private static volatile boolean shutdown = false;
|
||||
|
||||
private static void addAction(Action action) {
|
||||
queue.add(action);
|
||||
if (running.compareAndSet(false, true)) {
|
||||
new FileSaver().start();
|
||||
}
|
||||
}
|
||||
|
||||
private static void doSave(Map<Path, String> map) {
|
||||
for (Map.Entry<Path, String> entry : map.entrySet()) {
|
||||
saveSync(entry.getKey(), entry.getValue());
|
||||
@@ -53,10 +57,7 @@ public final class FileSaver extends Thread {
|
||||
|
||||
ShutdownHook.ensureInstalled();
|
||||
|
||||
queue.add(Pair.pair(file, content));
|
||||
if (running.compareAndSet(false, true)) {
|
||||
new FileSaver().start();
|
||||
}
|
||||
addAction(new DoSave(file, content));
|
||||
}
|
||||
|
||||
public static void saveSync(Path file, String content) {
|
||||
@@ -70,7 +71,17 @@ public final class FileSaver extends Thread {
|
||||
|
||||
public static void shutdown() {
|
||||
shutdown = true;
|
||||
queue.add(SHUTDOWN);
|
||||
queue.add(Shutdown.INSTANCE);
|
||||
}
|
||||
|
||||
/// Wait for all saves to complete.
|
||||
///
|
||||
/// This method should be called before the [#shutdown()] method.
|
||||
public static void waitForAllSaves() throws InterruptedException {
|
||||
assert !shutdown;
|
||||
Wait wait = new Wait();
|
||||
addAction(wait);
|
||||
wait.await();
|
||||
}
|
||||
|
||||
private FileSaver() {
|
||||
@@ -92,34 +103,46 @@ public final class FileSaver extends Thread {
|
||||
runningLock.lock();
|
||||
try {
|
||||
HashMap<Path, String> map = new HashMap<>();
|
||||
ArrayList<Pair<Path, String>> buffer = new ArrayList<>();
|
||||
ArrayList<Action> buffer = new ArrayList<>();
|
||||
ArrayList<Wait> waits = new ArrayList<>();
|
||||
|
||||
while (!stopped) {
|
||||
if (shutdown) {
|
||||
stopCurrentSaver();
|
||||
} else {
|
||||
Pair<Path, String> head = queue.poll(30, TimeUnit.SECONDS);
|
||||
if (head == null || head == SHUTDOWN) {
|
||||
stopCurrentSaver();
|
||||
} else {
|
||||
map.put(head.getKey(), head.getValue());
|
||||
Action head = queue.poll(30, TimeUnit.SECONDS);
|
||||
if (head instanceof DoSave save) {
|
||||
map.put(save.file(), save.content());
|
||||
//noinspection BusyWait
|
||||
Thread.sleep(200); // Waiting for more changes
|
||||
} else if (head instanceof Wait wait) {
|
||||
waits.add(wait);
|
||||
} else if (head == null || head instanceof Shutdown) {
|
||||
// Shutdown or timeout
|
||||
stopCurrentSaver();
|
||||
}
|
||||
}
|
||||
|
||||
while (queue.drainTo(buffer) > 0) {
|
||||
for (Pair<Path, String> pair : buffer) {
|
||||
if (pair == SHUTDOWN)
|
||||
for (Action action : buffer) {
|
||||
if (action instanceof DoSave save) {
|
||||
map.put(save.file(), save.content());
|
||||
} else if (action instanceof Wait wait) {
|
||||
waits.add(wait);
|
||||
} else if (action instanceof Shutdown) {
|
||||
stopCurrentSaver();
|
||||
else
|
||||
map.put(pair.getKey(), pair.getValue());
|
||||
}
|
||||
}
|
||||
buffer.clear();
|
||||
}
|
||||
|
||||
doSave(map);
|
||||
map.clear();
|
||||
|
||||
for (Wait wait : waits) {
|
||||
wait.countDown();
|
||||
}
|
||||
waits.clear();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
throw new AssertionError("This thread cannot be interrupted", e);
|
||||
@@ -128,6 +151,28 @@ public final class FileSaver extends Thread {
|
||||
}
|
||||
}
|
||||
|
||||
private sealed interface Action {
|
||||
}
|
||||
|
||||
private record DoSave(Path file, String content) implements Action {
|
||||
}
|
||||
|
||||
private static final class Wait implements Action {
|
||||
private final CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
public void await() throws InterruptedException {
|
||||
latch.await();
|
||||
}
|
||||
|
||||
public void countDown() {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
private enum Shutdown implements Action {
|
||||
INSTANCE
|
||||
}
|
||||
|
||||
private static final class ShutdownHook extends Thread {
|
||||
|
||||
static {
|
||||
@@ -144,9 +189,10 @@ public final class FileSaver extends Thread {
|
||||
runningLock.lock();
|
||||
try {
|
||||
HashMap<Path, String> map = new HashMap<>();
|
||||
for (Pair<Path, String> pair : queue) {
|
||||
if (pair != SHUTDOWN)
|
||||
map.put(pair.getKey(), pair.getValue());
|
||||
for (Action action : queue) {
|
||||
if (action instanceof DoSave save) {
|
||||
map.put(save.file(), save.content());
|
||||
}
|
||||
}
|
||||
doSave(map);
|
||||
} finally {
|
||||
|
||||
@@ -63,6 +63,16 @@ public final class NativePatcher {
|
||||
});
|
||||
}
|
||||
|
||||
// https://github.com/LWJGL/lwjgl3/issues/1111
|
||||
public static boolean needPatchMemoryUtil(Version version, int javaVersion) {
|
||||
return javaVersion >= 25 && javaVersion <= 26 && version.getLibraries().stream().anyMatch(library ->
|
||||
"org.lwjgl".equals(library.getGroupId())
|
||||
&& "lwjgl".equals(library.getArtifactId())
|
||||
&& "3.4.1".equals(library.getVersion())
|
||||
&& library.getClassifier() == null
|
||||
);
|
||||
}
|
||||
|
||||
public static Version patchNative(DefaultGameRepository repository,
|
||||
Version version, String gameVersion,
|
||||
JavaRuntime javaVersion,
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.util;
|
||||
|
||||
import javafx.beans.value.WritableValue;
|
||||
import javafx.scene.image.Image;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
@@ -33,18 +34,20 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
/// @author Glavo
|
||||
public abstract class RemoteImageLoader {
|
||||
private final DownloadProvider downloadProvider;
|
||||
private final Map<URI, WeakReference<Image>> cache = new HashMap<>();
|
||||
private final Map<URI, List<WeakReference<WritableValue<Image>>>> pendingRequests = new HashMap<>();
|
||||
private final WeakHashMap<WritableValue<Image>, URI> reverseLookup = new WeakHashMap<>();
|
||||
|
||||
public RemoteImageLoader() {
|
||||
public RemoteImageLoader(DownloadProvider downloadProvider) {
|
||||
this.downloadProvider = downloadProvider;
|
||||
}
|
||||
|
||||
protected @Nullable Image getPlaceholder() {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected abstract @NotNull Task<Image> createLoadTask(@NotNull URI uri);
|
||||
protected abstract @NotNull Task<Image> createLoadTask(@NotNull List<URI> uris);
|
||||
|
||||
@FXThread
|
||||
public void load(@NotNull WritableValue<Image> writableValue, String url) {
|
||||
@@ -82,7 +85,7 @@ public abstract class RemoteImageLoader {
|
||||
}
|
||||
}
|
||||
|
||||
createLoadTask(uri).whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
createLoadTask(downloadProvider.injectURLWithCandidates(url)).whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
Image image;
|
||||
if (exception == null) {
|
||||
image = result;
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import org.jackhuang.hmcl.upgrade.UpdateHandler;
|
||||
import org.jackhuang.hmcl.util.io.JarUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
/// @author Glavo
|
||||
public final class Restarter {
|
||||
|
||||
/// Restart the current application.
|
||||
public static void restartSelf() throws IOException {
|
||||
LOG.info("Restarting HMCL");
|
||||
|
||||
Path thisJar = JarUtils.thisJarPath();
|
||||
if (thisJar == null) {
|
||||
throw new IOException("Failed to find current HMCL location");
|
||||
}
|
||||
|
||||
UpdateHandler.startJava(thisJar);
|
||||
}
|
||||
|
||||
private Restarter() {
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@
|
||||
},
|
||||
{
|
||||
"title" : "jsoup",
|
||||
"subtitle" : "Copyright © 2009 - 2024 Jonathan Hedley (https://jsoup.org/)\nLicensed under the MIT License.",
|
||||
"subtitle" : "Copyright © 2009 - 2025 Jonathan Hedley (https://jsoup.org/)\nLicensed under the MIT License.",
|
||||
"externalLink" : "https://jsoup.org/"
|
||||
},
|
||||
{
|
||||
@@ -66,17 +66,17 @@
|
||||
},
|
||||
{
|
||||
"title" : "Java Animated PNG",
|
||||
"subtitle" : "Copyright (C) 2015 Andrew Ellerton.\nLicensed under the Apache 2.0 License.",
|
||||
"subtitle" : "Copyright © 2015 Andrew Ellerton.\nLicensed under the Apache 2.0 License.",
|
||||
"externalLink" : "https://github.com/aellerton/japng"
|
||||
},
|
||||
{
|
||||
"title": "Terracotta",
|
||||
"subtitle": "Copyright (C) 2025 Burning_TNT.\nAll rights reserved.",
|
||||
"subtitle": "Copyright © 2025 Burning_TNT.\nLicensed under the AGPL 3.0 License.",
|
||||
"externalLink": "https://github.com/burningtnt/Terracotta"
|
||||
},
|
||||
{
|
||||
"title": "EasyTier",
|
||||
"subtitle": "Copyright 2024-present Easytier Programme within The Commons Conservancy.\nLicensed under the LGPL 3.0 License.",
|
||||
"subtitle": "Copyright © 2024-present Easytier Programme within The Commons Conservancy.\nLicensed under the LGPL 3.0 License.",
|
||||
"externalLink": "https://github.com/EasyTier/EasyTier"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
|
||||
.scroll-bar .thumb {
|
||||
-fx-fill: -monet-surface-tint;
|
||||
-fx-arc-width: 0;
|
||||
-fx-arc-height: 0;
|
||||
-fx-arc-width: 8;
|
||||
-fx-arc-height: 8;
|
||||
}
|
||||
|
||||
.title-label {
|
||||
@@ -445,6 +445,7 @@
|
||||
-fx-max-height: 55px;
|
||||
-fx-min-height: 55px;
|
||||
-fx-background-color: -monet-primary-container;
|
||||
-fx-opacity: 1; /* Override the opacity of disabled button */
|
||||
-fx-cursor: hand;
|
||||
}
|
||||
|
||||
@@ -912,28 +913,22 @@
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
|
||||
.jfx-progress-bar > .track {
|
||||
-fx-background-color: -monet-secondary-container;
|
||||
}
|
||||
|
||||
.jfx-progress-bar > .bar,
|
||||
.jfx-progress-bar:indeterminate > .bar{
|
||||
.jfx-progress-bar > .bar {
|
||||
-fx-background-color: -monet-primary;
|
||||
-fx-padding: 1.5;
|
||||
}
|
||||
|
||||
.jfx-progress-bar > .secondary-bar,
|
||||
.jfx-progress-bar:indeterminate > .secondary-bar {
|
||||
-fx-background-color: -monet-secondary;
|
||||
-fx-padding: 2;
|
||||
}
|
||||
|
||||
.jfx-progress-bar > .track,
|
||||
.jfx-progress-bar > .bar {
|
||||
-fx-background-radius: 0;
|
||||
-fx-background-radius: 2;
|
||||
-fx-background-insets: 0;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* JFX Textfield *
|
||||
@@ -1170,7 +1165,7 @@
|
||||
-fx-background-insets: 0px;
|
||||
|
||||
-jfx-toggle-color: -monet-primary;
|
||||
-jfx-toggle-line-color: -monet-primary-container;
|
||||
-jfx-toggle-line-color: -monet-secondary-container;
|
||||
-jfx-untoggle-color: -monet-outline;
|
||||
-jfx-untoggle-line-color: -monet-surface-container-highest;
|
||||
-jfx-size: 10;
|
||||
@@ -1456,20 +1451,8 @@
|
||||
-fx-stroke: -monet-primary-container;
|
||||
}
|
||||
|
||||
.jfx-spinner:determinate .percentage {
|
||||
-fx-fill: -monet-on-surface-variant;
|
||||
}
|
||||
|
||||
.first-spinner {
|
||||
-jfx-radius: 20;
|
||||
}
|
||||
|
||||
.first-spinner .arc {
|
||||
-fx-stroke-width: 5.0;
|
||||
}
|
||||
|
||||
.small-spinner {
|
||||
-jfx-radius: 10;
|
||||
-jfx-radius: 9;
|
||||
}
|
||||
|
||||
.small-spinner .arc {
|
||||
@@ -1477,59 +1460,7 @@
|
||||
}
|
||||
|
||||
.small-spinner-pane .jfx-spinner {
|
||||
-jfx-radius: 10;
|
||||
}
|
||||
|
||||
.small-spinner-pane .jfx-spinner .arc {
|
||||
-fx-stroke-width: 3.0;
|
||||
}
|
||||
|
||||
.second-spinner {
|
||||
-jfx-radius: 30;
|
||||
}
|
||||
|
||||
.second-spinner .arc {
|
||||
-fx-stroke-width: 5.0;
|
||||
}
|
||||
|
||||
.third-spinner {
|
||||
-jfx-radius: 40;
|
||||
}
|
||||
|
||||
.third-spinner .arc {
|
||||
-fx-stroke-width: 5.0;
|
||||
}
|
||||
|
||||
.fourth-spinner {
|
||||
-jfx-radius: 50;
|
||||
}
|
||||
|
||||
.fourth-spinner .arc {
|
||||
-fx-stroke-width: 5.0;
|
||||
}
|
||||
|
||||
.fifth-spinner {
|
||||
-jfx-radius: 60;
|
||||
}
|
||||
|
||||
.fifth-spinner .arc {
|
||||
-fx-stroke-width: 5.0;
|
||||
}
|
||||
|
||||
.sixth-spinner {
|
||||
-jfx-radius: 70;
|
||||
}
|
||||
|
||||
.sixth-spinner .arc {
|
||||
-fx-stroke-width: 5.0;
|
||||
}
|
||||
|
||||
.seventh-spinner {
|
||||
-jfx-radius: 80;
|
||||
}
|
||||
|
||||
.seventh-spinner .arc {
|
||||
-fx-stroke-width: 5.0;
|
||||
-jfx-radius: 9;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -1941,6 +1872,10 @@
|
||||
-fx-padding: 10 16 10 16;
|
||||
}
|
||||
|
||||
.line-component .subtitle-label {
|
||||
-fx-text-fill: -monet-on-surface-variant;
|
||||
}
|
||||
|
||||
.line-component.no-padding .line-component-container {
|
||||
-fx-padding: 0;
|
||||
}
|
||||
@@ -1970,12 +1905,20 @@
|
||||
-fx-font-size: 13px;
|
||||
}
|
||||
|
||||
.line-select-button .menu-container .title-label {
|
||||
-fx-text-fill: -monet-on-surface;
|
||||
}
|
||||
|
||||
.line-select-button .menu-container .subtitle-label {
|
||||
-fx-text-fill: -monet-on-surface-variant;
|
||||
}
|
||||
|
||||
.line-select-button .menu-container:selected .title-label {
|
||||
-fx-text-fill: -monet-primary;
|
||||
}
|
||||
|
||||
.line-select-button .menu-container:selected .subtitle-label {
|
||||
-fx-text-fill: -monet-primary;
|
||||
-fx-text-fill: -monet-primary-container;
|
||||
}
|
||||
|
||||
.line-toggle-button .jfx-toggle-button {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
# Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
about=About
|
||||
about.copyright=Copyright
|
||||
about.copyright.statement=Copyright © 2026 huangyuhui
|
||||
about.copyright.statement=Copyright © 2013-2026 huangyuhui and contributors.
|
||||
about.author=Author
|
||||
about.author.statement=bilibili @huanghongxun
|
||||
about.claim=EULA
|
||||
@@ -334,6 +334,10 @@ curse.category.4550=Quests
|
||||
curse.category.4555=World Gen
|
||||
curse.category.4552=Scripts
|
||||
|
||||
curse.category.6553=Realistic
|
||||
curse.category.6554=Fantasy
|
||||
curse.category.6555=Vanilla
|
||||
|
||||
curse.sort.author=Author
|
||||
curse.sort.date_created=Date Created
|
||||
curse.sort.last_updated=Last Updated
|
||||
@@ -439,7 +443,7 @@ You can continue to keep the old version of Java.\
|
||||
HMCL can recognize and manage multiple Java versions and will automatically select the appropriate Java for you based on the game version.
|
||||
fatal.deprecated_java_version.download_link=Download Java %d
|
||||
fatal.samba=If you opened Hello Minecraft! Launcher from a Samba network drive, some features might not be working. Please try updating your Java or moving the launcher to another directory.
|
||||
fatal.illegal_char=Your user path contains an illegal character "=". You will not be able to use authlib-injector or change the skin of your offline account.
|
||||
fatal.illegal_char=Your working directory contains an illegal character "=". You will not be able to use authlib-injector or change the skin of your offline account.
|
||||
fatal.unsupported_platform=Minecraft is not fully supported on your platform yet, so you may experience missing features or even be unable to launch the game.\n\
|
||||
\n\
|
||||
If you cannot launch Minecraft 1.17 and later, you can try switching the "Renderer" to "Mesa LLVMpipe" in "Global/Instance-specific Settings → Advanced Settings" to use CPU rendering for better compatibility.
|
||||
@@ -794,7 +798,7 @@ launch.advice=%s Do you still want to continue to launch?
|
||||
launch.advice.multi=The following problems were detected:\n\n%s\n\nThese problems may prevent you from launching the game or affect gaming experience.\nDo you still want to continue to launch?
|
||||
launch.advice.java.auto=The current Java version is not compatible with the instance.\n\nClick "Yes" to automatically choose the most compatible Java version. Or, you can navigate to "Global/Instance-specific Settings → Java" to choose one yourself.
|
||||
launch.advice.java.modded_java_7=Minecraft 1.7.2 and previous versions require Java 7 or earlier.
|
||||
launch.advice.cleanroom=Cleanroom can only be run on Java 21 or later. Please use Java 21 or later versions.
|
||||
launch.advice.cleanroom=Cleanroom %2$s can only be run on Java %1$s or later. Please use Java %1$s or later versions.
|
||||
launch.advice.corrected=We have resolved the Java problem. If you still want to use your choice of Java version, you can disable "Do not check JVM compatibility" in "Global/Instance-specific Settings → Advanced Settings".
|
||||
launch.advice.uncorrected=If you still want to use your choice of Java version, you can disable "Do not check JVM compatibility" in "Global/Instance-specific Settings → Advanced Settings".
|
||||
launch.advice.different_platform=The 64-bit Java version is recommended for your device, but you have installed a 32-bit one.
|
||||
@@ -804,6 +808,8 @@ launch.advice.forge37_0_60=Forge versions prior to 37.0.60 are not compatible wi
|
||||
launch.advice.java8_1_13=Minecraft 1.13 and later can only be run on Java 8 or later. Please use Java 8 or later versions.
|
||||
launch.advice.java8_51_1_13=Minecraft 1.13 may crash on Java 8 versions prior to 1.8.0_51. Please install the latest Java 8 version.
|
||||
launch.advice.java9=You cannot launch Minecraft 1.12 or earlier with Java 9 or later. Please use Java 8 instead.
|
||||
launch.advice.lwjgl_3_4_1=The current game version has a known issue that may cause stuttering in the game.\nHMCL can automatically optimize the game performance (using a Java Agent).\nDo you allow HMCL to automatically optimize the game?\nIf you encounter issues, you can disable the "Allow HMCL to modify the game" option in "Settings → General".
|
||||
launch.advice.lwjgl_3_4_1.title=Allow HMCL to automatically optimize the game?
|
||||
launch.advice.modded_java=Some mods may not be compatible with newer Java versions. It is recommended to use Java %1$s to launch Minecraft %2$s.
|
||||
launch.advice.modlauncher8=The Forge version you are using is not compatible with the current Java version. Please try updating Forge.
|
||||
launch.advice.newer_java=You are using an older Java version to launch the game. It is recommended to update to Java 8, otherwise some mods may cause the game to crash.
|
||||
@@ -840,6 +846,8 @@ launcher.agreement=ToS and EULA
|
||||
launcher.agreement.accept=Accept
|
||||
launcher.agreement.decline=Decline
|
||||
launcher.agreement.hint=You must agree to the EULA to use this software.
|
||||
launcher.april_fools.switch_lzh=HMCL Launcher now supports Classical Chinese, do you want to switch to Classical Chinese mode?\nYou can switch back to English in "置設 → 貫用 → 文".
|
||||
launcher.april_fools.switch_lzh.confirm=Confirm switching to Classical Chinese mode?\nClick "OK" to exit HMCL and restart it in Classical Chinese mode; click "Cancel" to keep the current language and enter HMCL homepage.
|
||||
launcher.background=Background Image
|
||||
launcher.background.choose=Choose background image
|
||||
launcher.background.classic=Classic
|
||||
@@ -1410,6 +1418,8 @@ settings.game.working_directory.hint=Enable the "Isolated" option in "Working Di
|
||||
settings.icon=Icon
|
||||
|
||||
settings.launcher=Launcher Settings
|
||||
settings.launcher.allow_auto_agent=Allow HMCL to modify the game
|
||||
settings.launcher.allow_auto_agent.subtitle=Allow HMCL to modify the game by attaching a Java Agent to improve the gaming experience.
|
||||
settings.launcher.appearance=Appearance
|
||||
settings.launcher.brightness=Theme Mode
|
||||
settings.launcher.brightness.auto=Follow System Settings
|
||||
@@ -1417,6 +1427,7 @@ settings.launcher.brightness.dark=Dark Mode
|
||||
settings.launcher.brightness.light=Light Mode
|
||||
settings.launcher.common_path.tooltip=HMCL will put all game assets and dependencies here. If there are existing libraries in the game directory, then HMCL will prefer to use them first.
|
||||
settings.launcher.debug=Debug
|
||||
settings.launcher.disable_april_fools=Do not enable April Fools features
|
||||
settings.launcher.disable_auto_game_options=Do not switch game language
|
||||
settings.launcher.download=Download
|
||||
settings.launcher.download.threads=Threads
|
||||
@@ -1587,6 +1598,8 @@ update.channel.nightly.hint=You are currently using a Nightly channel build of t
|
||||
update.channel.nightly.title=Nightly Channel Notice
|
||||
update.channel.stable=Release
|
||||
update.checking=Checking for updates
|
||||
update.disable_auto_show_update_dialog=Do not show update dialog automatically
|
||||
update.disable_auto_show_update_dialog.subtitle=Enable this option to prevent HMCL from automatically showing the update dialog.
|
||||
update.failed=Failed to update
|
||||
update.found=Update Available!
|
||||
update.newest_version=Latest version: %s
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
# Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -21,7 +21,6 @@
|
||||
|
||||
about=Acerca de
|
||||
about.copyright=Copyright
|
||||
about.copyright.statement=Copyright © 2026 huangyuhui.
|
||||
about.author=Autor
|
||||
about.author.statement=bilibili @huanghongxun
|
||||
about.claim=EULA
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
# Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -20,7 +20,6 @@
|
||||
|
||||
about=について
|
||||
about.copyright=著作権
|
||||
about.copyright.statement=Copyright © 2026 huangyuhui
|
||||
about.author=著者
|
||||
about.author.statement=bilibili @huanghongxun
|
||||
about.claim=EULA
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
# Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -20,7 +20,6 @@
|
||||
|
||||
about=有涉
|
||||
about.copyright=持權
|
||||
about.copyright.statement=© 2026 huangyuhui 持權
|
||||
about.author=作者
|
||||
about.author.statement=嗶站 @huanghongxun
|
||||
about.claim=客契
|
||||
@@ -580,6 +579,8 @@ launch.advice.forge37_0_60=鍛三七點〇點五九與舊者弗兼爪哇十七
|
||||
launch.advice.java8_1_13=礦藝一點一三以晉但行於爪哇八與晉者。宜行以爪哇八與晉者。
|
||||
launch.advice.java8_51_1_13=爪哇之前一點八點〇之五一者或崩礦藝一點一三。宜新爪哇於一點八點〇之五一以晉,然後復啟。
|
||||
launch.advice.java9=礦藝之前一點一三而有改囊者弗見兼於爪哇九與晉者。宜啟以爪哇八。
|
||||
launch.advice.lwjgl_3_4_1=戲之是版有已知之謬,行戲之時或致頓滯。\nHMCL 可假爪哇 Agent 自優其戲,以弭是患。君其許 HMCL 自優其戲乎?\n誠有謬,可至於「置設 → 貫用」,廢「許改戲」之項。
|
||||
launch.advice.lwjgl_3_4_1.title=君其許 HMCL 自優其戲乎?
|
||||
launch.advice.modded_java=改囊或弗見兼於爪哇之新者。宜以爪哇 %s 啟礦藝 %s。
|
||||
launch.advice.modlauncher8=鍛版之所行弗兼於爪哇之所行。宜新鍛。
|
||||
launch.advice.newer_java=余識君之啟戲以舊爪哇也,蓋改囊或是以崩戲也。宜新爪哇於八,然後復啟。
|
||||
@@ -976,7 +977,10 @@ repositories.chooser=闕 JavaFX。HMCL 須之以行。\n擊「善」而引 JavaF
|
||||
repositories.chooser.title=擇 JavaFX 之源
|
||||
|
||||
resourcepack=資囊
|
||||
resourcepack.add=增資囊
|
||||
resourcepack.download=引資囊
|
||||
resourcepack.download.title=引資囊 - %1s
|
||||
resourcepack.manage=司資囊
|
||||
|
||||
reveal.in_file_manager=覽於理案臺
|
||||
|
||||
@@ -1124,6 +1128,8 @@ settings.game.working_directory.hint=夫于「運徑」擇「例獨」者,可
|
||||
settings.icon=戲徵
|
||||
|
||||
settings.launcher=啟者置設
|
||||
settings.launcher.allow_auto_agent=許改戲
|
||||
settings.launcher.allow_auto_agent.subtitle=許 HMCL 假附加爪哇 Agent 以改戲,而優其戲感。
|
||||
settings.launcher.appearance=外觀
|
||||
settings.launcher.common_path.tooltip=啟者置戲資與府庫於是。倘戲有成案,則弗取公庫之案。
|
||||
settings.launcher.debug=勘誤
|
||||
@@ -1141,7 +1147,7 @@ settings.launcher.font.anti_aliasing.auto=自調
|
||||
settings.launcher.font.anti_aliasing.gray=灰階
|
||||
settings.launcher.font.anti_aliasing.lcd=子像素
|
||||
settings.launcher.general=貫用
|
||||
settings.launcher.language=文 (複啟而效)
|
||||
settings.launcher.language=文
|
||||
settings.launcher.launcher_log.export=錄出啟者之誌
|
||||
settings.launcher.launcher_log.export.failed=錄誌出而未成
|
||||
settings.launcher.launcher_log.export.success=誌既存於「%s」
|
||||
@@ -1194,6 +1200,8 @@ sponsor.hmcl=Hello Minecraft! Launcher,乃一免費,自由,開源之礦藝
|
||||
system.architecture=大構
|
||||
system.operating_system=械網
|
||||
|
||||
terracotta=眾戲
|
||||
|
||||
unofficial.hint=子方用非官築之 HMCL。余無能證其安,請君自辨。
|
||||
|
||||
update=啟者迭更
|
||||
@@ -1211,6 +1219,8 @@ update.channel.nightly.hint=君所用之 HMCL 乃預版也。預版之更迭常
|
||||
update.channel.nightly.title=預版告
|
||||
update.channel.stable=定版
|
||||
update.checking=甫察其新
|
||||
update.disable_auto_show_update_dialog=弗自顯迭更窗
|
||||
update.disable_auto_show_update_dialog.subtitle=啟此,HMCL 將弗自顯迭更窗。
|
||||
update.failed=迭更未成
|
||||
update.found=尋得新者
|
||||
update.newest_version=至新:%s
|
||||
@@ -1220,6 +1230,8 @@ update.note=开發版與預版,虽增功、修阙,然亦恐生他患。
|
||||
update.latest=當版至新
|
||||
update.no_browser=無啟瀏覽器。址固鈔訖,君可自粘之以啟頁。
|
||||
update.tooltip=迭更
|
||||
update.preview=先覽 HMCL 之版
|
||||
update.preview.subtitle=啟此,君可先得 HMCL 之新,試之於未行。
|
||||
|
||||
version=戲
|
||||
version.name=戲例之名
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
# Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -20,7 +20,6 @@
|
||||
|
||||
about=О лаунчере
|
||||
about.copyright=Авторские права
|
||||
about.copyright.statement=Авторские права © 2026 huangyuhui.
|
||||
about.author=Автор
|
||||
about.author.statement=bilibili @huanghongxun
|
||||
about.claim=EULA
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
# Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -20,7 +20,6 @@
|
||||
|
||||
about=Про програму
|
||||
about.copyright=Авторське право
|
||||
about.copyright.statement=Авторське право © 2026 huangyuhui
|
||||
about.author=Автор
|
||||
about.author.statement=bilibili @huanghongxun
|
||||
about.claim=Ліцензійна угода з кінцевим користувачем (EULA)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
# Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
about=關於
|
||||
about.copyright=著作權
|
||||
about.copyright.statement=著作權所有 © 2026 huangyuhui
|
||||
about.copyright.statement=著作權所有 © 2013-2026 huangyuhui 與貢獻者
|
||||
about.author=作者
|
||||
about.author.statement=bilibili @huanghongxun
|
||||
about.claim=使用者協議
|
||||
@@ -30,7 +30,7 @@ about.legal=法律宣告
|
||||
about.thanks_to=鳴謝
|
||||
about.thanks_to.bangbang93.statement=提供 BMCLAPI 下載來源。請贊助支援 BMCLAPI!
|
||||
about.thanks_to.burningtnt.statement=為 HMCL 貢獻許多技術支援
|
||||
about.thanks_to.contributors=所有透過 Issue、Pull Request 等管道參與本項目的貢獻者
|
||||
about.thanks_to.contributors=所有透過 Issue、Pull Request 等管道參與本專案的貢獻者
|
||||
about.thanks_to.contributors.statement=沒有開源社群的支援,HMCL 無法走到今天
|
||||
about.thanks_to.gamerteam.statement=提供預設背景圖
|
||||
about.thanks_to.glavo.statement=負責 HMCL 的日常維護
|
||||
@@ -104,7 +104,7 @@ account.methods=登入方式
|
||||
account.methods.authlib_injector=外部登入
|
||||
account.methods.microsoft=Microsoft 帳戶
|
||||
account.methods.microsoft.birth=如何變更帳戶出生日期
|
||||
account.methods.microsoft.code=代碼 (已自動複製)
|
||||
account.methods.microsoft.code=程式碼 (已自動複製)
|
||||
account.methods.microsoft.close_page=已完成 Microsoft 帳戶授權。啟動器將自動執行後續步驟。你現在可以關閉本頁面了。
|
||||
account.methods.microsoft.deauthorize=移除應用存取權
|
||||
account.methods.microsoft.error.add_family=請點擊 <a href="https://support.microsoft.com/account-billing/837badbc-999e-54d2-2617-d19206b9540a">此處</a> 更改你的帳戶出生日期,使年齡滿 18 歲以上,或將帳戶加入到家庭中。
|
||||
@@ -330,6 +330,10 @@ curse.category.4550=任務
|
||||
curse.category.4555=世界生成
|
||||
curse.category.4552=指令碼
|
||||
|
||||
curse.category.6553=寫實
|
||||
curse.category.6554=幻想
|
||||
curse.category.6555=原生
|
||||
|
||||
curse.sort.author=作者
|
||||
curse.sort.date_created=建立日期
|
||||
curse.sort.last_updated=最近更新
|
||||
@@ -408,7 +412,7 @@ fatal.deprecated_java_version=HMCL 未來需要 Java 17 或更高版本才能執
|
||||
fatal.deprecated_java_version.update=更高版本的 HMCL 需要 Java 17 或更高版本才能執行,請安裝最新版本的 Java 以確保 HMCL 能夠完成升級。\n你可以繼續保留舊版本 Java。HMCL 能夠識別與管理多個 Java,並會自動根據遊戲版本為你選取合適的 Java。
|
||||
fatal.deprecated_java_version.download_link=下載 Java %d
|
||||
fatal.samba=如果您正在透過 Samba 共亯的目錄中開啟 Hello Minecraft! Launcher,啟動器可能無法正常工作,請嘗試更新您的 Java 或在本機目錄內開啟 HMCL。
|
||||
fatal.illegal_char=由於您的使用者目錄路徑中存在無效字元『=』,您將無法使用外部登入帳戶以及離線登入更換外觀功能。
|
||||
fatal.illegal_char=由於您的執行路徑中存在無效字元『=』,您將無法使用外部登入帳戶以及離線登入更換外觀功能。
|
||||
fatal.unsupported_platform=Minecraft 尚未對您的平臺提供完善支援,所以可能影響遊戲體驗或無法啟動遊戲。\n\
|
||||
若無法啟動 Minecraft 1.17 及更高版本,可以嘗試在「(全域/實例特定) 遊戲設定 → 進階設定 → 除錯選項」中將「繪製器」切換為「Mesa LLVMpipe」,以獲得更好的相容性。
|
||||
fatal.unsupported_platform.loongarch=Hello Minecraft! Launcher 已為龍芯提供支援。\n如果遇到問題,你可以點擊右上角幫助按鈕進行求助。
|
||||
@@ -479,7 +483,7 @@ game.crash.reason.mod_resolution_mod_version.any=%1$s (任意版本)
|
||||
game.crash.reason.forge_repeat_installation=目前遊戲由於 Forge 重複安裝,無法繼續執行。<a href="https://github.com/HMCL-dev/HMCL/issues/1880">此為已知問題</a>\n建議將日誌上傳並回報至 GitHub,以便我們找到更多線索並修復此問題。\n目前你可以在「實例管理 → 自動安裝」中移除 Forge 並重新安裝。
|
||||
game.crash.reason.optifine_repeat_installation=目前遊戲由於重複安裝 OptiFine,無法繼續執行。\n請刪除模組目錄下的 OptiFine 或前往「實例管理 → 自動安裝」移除安裝的 OptiFine。
|
||||
game.crash.reason.optifine_is_not_compatible_with_forge=目前遊戲由於 OptiFine 與目前版本的 Forge 不相容,導致了遊戲崩潰。\n請前往 <a href="https://optifine.net/downloads">OptiFine 官網</a>查看 OptiFine 所相容的 Forge 版本,並嚴格按照對應版本重新安裝遊戲,或在「實例管理 → 自動安裝」中更換版本。\n經測試,Forge 版本過高或過低都可能導致崩潰。
|
||||
game.crash.reason.night_config_fixes=目前遊戲由於 Night Config 庫的一些問題,無法繼續執行。\n你可以嘗試安裝 <a href="https://www.curseforge.com/minecraft/mc-mods/night-config-fixes">Night Config Fixes</a> 模組,這或許能幫助你解決這個問題。\n了解更多,可訪問該模組的 <a href="https://github.com/Fuzss/nightconfigfixes">GitHub 倉庫</a>。
|
||||
game.crash.reason.night_config_fixes=目前遊戲由於 Night Config 庫的一些問題,無法繼續執行。\n你可以嘗試安裝 <a href="https://www.curseforge.com/minecraft/mc-mods/night-config-fixes">Night Config Fixes</a> 模組,這或許能幫助你解決這個問題。\n了解更多,可訪問該模組的 <a href="https://github.com/Fuzss/nightconfigfixes">GitHub 儲存庫</a>。
|
||||
game.crash.reason.mod_files_are_decompressed=目前遊戲由於模組檔案被解壓了,無法繼續執行。\n請直接把整個模組檔案放進模組目錄中即可。\n解壓模組檔案會導致遊戲出錯。請刪除模組目錄中已被解壓的模組,然後再啟動遊戲。
|
||||
game.crash.reason.too_many_mods_lead_to_exceeding_the_id_limit=目前遊戲由於你所安裝的模組過多,超出了遊戲的 ID 限制,無法繼續執行。\n請嘗試安裝<a href="https://www.curseforge.com/minecraft/mc-mods/jeid">JEID</a>等修正模組,或刪除部分大型模組。
|
||||
game.crash.reason.optifine_causes_the_world_to_fail_to_load=目前遊戲可能由於 OptiFine 而無法繼續執行。\n該問題只在特定 OptiFine 版本中出現,你可以嘗試在「實例管理 → 自動安裝」中更換 OptiFine 的版本。
|
||||
@@ -598,7 +602,7 @@ launch.advice=%s 是否繼續啟動?
|
||||
launch.advice.multi=檢測到以下問題:\n\n%s\n\n這些問題可能導致遊戲無法正常啟動或影響遊戲體驗,是否繼續啟動?
|
||||
launch.advice.java.auto=目前選取的 Java 版本不滿足遊戲要求,是否自動選取合適的 Java 版本?\n或者你可以到「(全域/實例特定) 遊戲設定 → 遊戲 Java」中選取一個合適的 Java 版本。
|
||||
launch.advice.java.modded_java_7=Minecraft 1.7.2 及更低版本需要 Java 7 及更低版本。
|
||||
launch.advice.cleanroom=Cleanroom 僅支援 Java 21 或更高版本。請使用 Java 21 或最新版本。
|
||||
launch.advice.cleanroom=Cleanroom %2$s 只能在 Java %1$s 或更高版本上運行。請使用 Java %1$s 或最新版本。
|
||||
launch.advice.corrected=我們已經修正了 Java 版本問題。如果你確實希望使用你自訂的 Java,你可以在「(全域/實例特定) 遊戲設定 → 進階設定」中往下滑,開啟「不檢查 Java 虛擬機與遊戲的相容性」。
|
||||
launch.advice.uncorrected=如果你確實希望使用你自訂的 Java,你可以在「(全域/實例特定) 遊戲設定 → 進階設定」中往下滑,開啟「不檢查 Java 虛擬機與遊戲的相容性」。
|
||||
launch.advice.different_platform=你正在使用 32 位元 Java 啟動遊戲。建議更換至 64 位元 Java。
|
||||
@@ -608,6 +612,8 @@ launch.advice.forge37_0_60=Forge 37.0.59 及更低版本與 Java 17 不相容。
|
||||
launch.advice.java8_1_13=Minecraft 1.13 及更高版本僅支援 Java 8 或更高版本。請使用 Java 8 或最新版本。
|
||||
launch.advice.java8_51_1_13=低於 1.8.0_51 的 Java 版本可能會導致 Minecraft 1.13 崩潰。建議你到 https://java.com 安裝最新版的 Java 8。
|
||||
launch.advice.java9=低於 (包含) 1.13 的有安裝模組的 Minecraft 版本不支援 Java 9 或更高版本。請使用 Java 8。
|
||||
launch.advice.lwjgl_3_4_1=當前遊戲版本存在已知問題,可能在遊戲中發生卡頓。\nHMCL 可以 (透過 Java Agent) 自動最佳化遊戲解決此問題。是否允許 HMCL 自動最佳化遊戲?\n如遇到問題,你可以在「設定 → 一般」中關閉「允許 HMCL 修改遊戲」選項。
|
||||
launch.advice.lwjgl_3_4_1.title=是否允許 HMCL 自動最佳化遊戲?
|
||||
launch.advice.modded_java=部分模組可能與高版本 Java 不相容。建議使用 Java %s 啟動 Minecraft %s。
|
||||
launch.advice.modlauncher8=你所使用的 Forge 版本與目前使用的 Java 不相容。請更新 Forge。
|
||||
launch.advice.newer_java=偵測到你正在使用舊版本 Java 啟動遊戲,這可能導致部分模組引發遊戲崩潰。建議更新至 Java 8 後再次啟動。
|
||||
@@ -644,6 +650,8 @@ launcher.agreement=使用者協議與免責宣告
|
||||
launcher.agreement.accept=同意
|
||||
launcher.agreement.decline=拒絕
|
||||
launcher.agreement.hint=同意本軟體的使用者協議與免責宣告以使用本軟體。
|
||||
launcher.april_fools.switch_lzh=HMCL 啟動器現已支援文言文,我們誠邀你參與體驗。是否要將介面語言切換至中文 (文言)?\n在切換語言後,你可以在「置設 → 貫用 → 文」中將語言切換回中文 (繁體)。
|
||||
launcher.april_fools.switch_lzh.confirm=確認切換語言?\n點擊「確定」後 HMCL 將自動重新啟動並將介面語言切換至中文 (文言);點擊「取消」將繼續使用中文 (繁體) 進入 HMCL 主頁。
|
||||
launcher.background=背景圖片
|
||||
launcher.background.choose=選取背景圖片
|
||||
launcher.background.classic=經典
|
||||
@@ -691,6 +699,7 @@ message.info=提示
|
||||
message.success=完成
|
||||
message.unknown=未知
|
||||
message.warning=警告
|
||||
message.question=確認
|
||||
|
||||
modpack=模組包
|
||||
modpack.choose=選取要安裝的遊戲模組包檔案
|
||||
@@ -862,7 +871,7 @@ mods.add.failed=新增模組「%s」失敗。
|
||||
mods.add.success=成功新增模組「%s」。
|
||||
mods.add.title=選取要新增的模組檔案
|
||||
mods.broken_dependency.title=損壞的相依模組
|
||||
mods.broken_dependency.desc=該相依模組曾經存在於模組倉庫中,但現在已被刪除,請嘗試其他下載源。
|
||||
mods.broken_dependency.desc=該相依模組曾經存在於模組儲存庫中,但現在已被刪除,請嘗試其他下載源。
|
||||
mods.category=類別
|
||||
mods.channel.alpha=Alpha
|
||||
mods.channel.beta=Beta
|
||||
@@ -884,7 +893,7 @@ mods.dependency.required=必需相依模組 (必須單獨下載,缺少可能
|
||||
mods.dependency.tool=相依庫 (必須單獨下載,缺少可能會導致遊戲無法啟動)
|
||||
mods.dependency.include=內建相依模組 (作者已經打包在模組檔中,無需單獨下載)
|
||||
mods.dependency.incompatible=不相容模組 (同時安裝此模組和正在下載的模組會導致遊戲無法啟動)
|
||||
mods.dependency.broken=損壞的相依模組 (該模組曾經存在於模組倉庫中,但現在已被刪除,請嘗試其他下載源)
|
||||
mods.dependency.broken=損壞的相依模組 (該模組曾經存在於模組儲存庫中,但現在已被刪除,請嘗試其他下載源)
|
||||
mods.disable=停用
|
||||
mods.download=模組下載
|
||||
mods.download.title=模組下載 - %1s
|
||||
@@ -1030,9 +1039,9 @@ profile.title=遊戲目錄
|
||||
profile.selected=已選取
|
||||
profile.use_relative_path=如可行,則對於遊戲目錄使用相對路徑
|
||||
|
||||
repositories.custom=自訂 Maven 倉庫 (%s)
|
||||
repositories.custom=自訂 Maven 儲存庫 (%s)
|
||||
repositories.maven_central=全球 (Maven Central)
|
||||
repositories.tencentcloud_mirror=中國大陸 (騰訊雲 Maven 倉庫)
|
||||
repositories.tencentcloud_mirror=中國大陸 (騰訊雲 Maven 儲存庫)
|
||||
repositories.chooser=缺少 JavaFX 執行環境。HMCL 需要 JavaFX 才能正常執行。\n點擊「確認」從指定下載源下載 JavaFX 執行時元件並開啟 HMCL。點擊「取消」退出程式。\n選取下載源:
|
||||
repositories.chooser.title=選取 JavaFX 下載源
|
||||
|
||||
@@ -1194,6 +1203,8 @@ settings.game.working_directory.hint=在「執行路徑」選項中選取「各
|
||||
settings.icon=遊戲圖示
|
||||
|
||||
settings.launcher=啟動器設定
|
||||
settings.launcher.allow_auto_agent=允許 HMCL 修改遊戲
|
||||
settings.launcher.allow_auto_agent.subtitle=允許 HMCL 透過附加 Java Agent 修改遊戲以改善遊戲體驗。
|
||||
settings.launcher.appearance=外觀
|
||||
settings.launcher.brightness=主題模式
|
||||
settings.launcher.brightness.auto=跟隨系統設定
|
||||
@@ -1201,6 +1212,7 @@ settings.launcher.brightness.dark=深色模式
|
||||
settings.launcher.brightness.light=淺色模式
|
||||
settings.launcher.common_path.tooltip=啟動器將所有遊戲資源及相依元件庫檔案放於此集中管理。如果遊戲目錄內有現成的將不會使用公共庫檔案。
|
||||
settings.launcher.debug=除錯
|
||||
settings.launcher.disable_april_fools=不啟用愚人節功能
|
||||
settings.launcher.disable_auto_game_options=不自動切換遊戲語言
|
||||
settings.launcher.download=下載
|
||||
settings.launcher.download.threads=執行緒數
|
||||
@@ -1280,7 +1292,7 @@ terracotta.difficulty.easiest=目前網路狀況極佳:稍等一下就成功
|
||||
terracotta.difficulty.simple=目前網路狀況較好:建立連線需要一段時間…
|
||||
terracotta.difficulty.medium=目前網路狀態中等:已啟用抗干擾備用線路,連線可能失敗
|
||||
terracotta.difficulty.tough=目前網路狀態極差:已啟用抗干擾備用線路,連線可能失敗
|
||||
terracotta.difficulty.estimate_only=連接成功率由房主和房客的 NAT 類型推算得到,僅供參考。
|
||||
terracotta.difficulty.estimate_only=連線成功率由房主和房客的 NAT 類型推算得到,僅供參考。
|
||||
terracotta.from_local.title=線上核心第三方下載管道
|
||||
terracotta.from_local.desc=在部分地區,內建的預設下載管道可能不穩定或連線緩慢
|
||||
terracotta.from_local.guide=您應下載名為 %s 的線上核心套件。下載完成後,請將檔案拖曳到目前介面來安裝。
|
||||
@@ -1339,7 +1351,7 @@ terracotta.status.fatal.network=未能下載線上核心。請檢查網路連接
|
||||
terracotta.status.fatal.install=嚴重錯誤:無法安裝線上核心
|
||||
terracotta.status.fatal.terracotta=嚴重錯誤:無法與線上核心通訊
|
||||
terracotta.status.fatal.unknown=嚴重錯誤:原因未知
|
||||
terracotta.player_list=玩家列表
|
||||
terracotta.player_list=玩家清單
|
||||
terracotta.player_anonymous=匿名玩家
|
||||
terracotta.player_kind.host=房主
|
||||
terracotta.player_kind.local=你
|
||||
@@ -1348,7 +1360,7 @@ terracotta.unsupported=多人聯機功能尚未支援目前平台。
|
||||
terracotta.unsupported.os.windows.old=多人聯機功能需要 Windows 10 或更高版本。請更新系統。
|
||||
terracotta.unsupported.arch.32bit=多人聯機功能不支援 32 位元系統。請更新至 64 位元系統。
|
||||
terracotta.unsupported.arch.loongarch64_ow=多人聯機功能不支援 Linux LoongArch64 舊世界發行版,請更新至新世界發行版 (如 AOSC OC)。
|
||||
terracotta.unsupported.region=多人聯機功能目前僅為中國內地用戶提供服務,在您所在地區可能不可用。
|
||||
terracotta.unsupported.region=多人聯機功能目前僅為中國內地使用者提供服務,在您所在地區可能不可用。
|
||||
|
||||
unofficial.hint=你正在使用第三方提供的 HMCL。我們無法保證其安全性,請注意甄別。
|
||||
|
||||
@@ -1367,6 +1379,8 @@ update.channel.nightly.hint=你正在使用 HMCL 預覽版。預覽版更新較
|
||||
update.channel.nightly.title=預覽版提示
|
||||
update.channel.stable=穩定版
|
||||
update.checking=正在檢查更新
|
||||
update.disable_auto_show_update_dialog=不自動顯示更新彈窗
|
||||
update.disable_auto_show_update_dialog.subtitle=啟用此選項,HMCL 將不會自動彈出更新彈窗。
|
||||
update.failed=更新失敗
|
||||
update.found=發現到更新
|
||||
update.newest_version=最新版本為:%s
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
# Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
about=关于
|
||||
about.copyright=版权
|
||||
about.copyright.statement=版权所有 © 2026 huangyuhui
|
||||
about.copyright.statement=版权所有 © 2013-2026 huangyuhui 及贡献者
|
||||
about.author=作者
|
||||
about.author.statement=bilibili @huanghongxun
|
||||
about.claim=用户协议
|
||||
@@ -332,6 +332,10 @@ curse.category.4550=任务
|
||||
curse.category.4555=世界生成
|
||||
curse.category.4552=脚本
|
||||
|
||||
curse.category.6553=写实
|
||||
curse.category.6554=幻想
|
||||
curse.category.6555=原生
|
||||
|
||||
curse.sort.author=作者
|
||||
curse.sort.date_created=创建日期
|
||||
curse.sort.last_updated=最近更新
|
||||
@@ -411,7 +415,7 @@ fatal.deprecated_java_version=HMCL 未来需要 Java 17 或更高版本才能运
|
||||
fatal.deprecated_java_version.update=更高版本的 HMCL 需要 Java 17 或更高版本才能运行,请安装最新版本的 Java 以确保 HMCL 能够完成升级。\n你可以继续保留旧版本 Java。HMCL 能够识别与管理多个 Java,并会自动根据游戏版本为你选择合适的 Java。
|
||||
fatal.deprecated_java_version.download_link=下载 Java %d
|
||||
fatal.samba=如果你正在通过 Samba 共享的文件夹中运行 Hello Minecraft! Launcher,启动器可能无法正常工作。请尝试更新你的 Java 或在本地文件夹内运行 HMCL。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。
|
||||
fatal.illegal_char=由于你的用户文件夹路径中存在非法字符“=”,你将无法使用外置登录账户以及离线登录更换皮肤功能。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。
|
||||
fatal.illegal_char=由于你的运行路径中存在非法字符“=”,你将无法使用外置登录账户以及离线登录更换皮肤功能。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。
|
||||
fatal.unsupported_platform=Minecraft 尚未对你的平台提供完善支持,所以可能影响游戏体验或无法启动游戏。\n\
|
||||
若无法启动 Minecraft 1.17 及更高版本,可以尝试在“(全局/实例特定) 游戏设置 → 高级设置 → 调试选项”中将“渲染器”切换为“Mesa LLVMpipe”,以获得更好的兼容性。\n\
|
||||
如遇到问题,你可以点击右上角帮助按钮进行求助。
|
||||
@@ -603,7 +607,7 @@ launch.advice=%s 是否继续启动?
|
||||
launch.advice.multi=检测到以下问题:\n\n%s\n\n这些问题可能导致游戏无法正常启动或影响游戏体验,是否继续启动?\n你可以点击右上角帮助按钮进行求助。
|
||||
launch.advice.java.auto=当前选择的 Java 版本不满足游戏要求。\n点击“是”即可由 HMCL 来自动选取合适的 Java 版本。\n或者你可以在“(全局/实例特定) 游戏设置 → 游戏 Java”中选择一个合适的 Java 版本。
|
||||
launch.advice.java.modded_java_7=Minecraft 1.7.2 及更低版本需要 Java 7 及更低版本。
|
||||
launch.advice.cleanroom=Cleanroom 只能在 Java 21 或更高版本上运行。请使用 Java 21 或最新版本。
|
||||
launch.advice.cleanroom=Cleanroom %2$s 只能在 Java %1$s 或更高版本上运行。请使用 Java %1$s 或最新版本。
|
||||
launch.advice.corrected=我们已经修复了 Java 版本问题。如果你确实希望使用你自定义的 Java,你可以在“(全局/实例特定) 游戏设置 → 高级设置”中往下滑,启用“不检查 Java 虚拟机与游戏的兼容性”。
|
||||
launch.advice.uncorrected=如果你确实希望使用你自定义的 Java,你可以在“(全局/实例特定) 游戏设置 → 高级设置”中往下滑,启用“不检查 Java 虚拟机与游戏的兼容性”。
|
||||
launch.advice.different_platform=你正在使用 32 位 Java 启动游戏。建议更换至 64 位 Java。
|
||||
@@ -613,6 +617,8 @@ launch.advice.forge37_0_60=Forge 37.0.59 及更低版本与 Java 17 不兼容。
|
||||
launch.advice.java8_1_13=Minecraft 1.13 及更高版本只能在 Java 8 或更高版本上运行。请使用 Java 8 或最新版本。
|
||||
launch.advice.java8_51_1_13=低于 1.8.0_51 的 Java 版本可能会导致 Minecraft 1.13 崩溃。建议更新 Java 至 1.8.0_51 或更高版本后再次启动。
|
||||
launch.advice.java9=低于 1.13 的有安装模组的 Minecraft 版本与 Java 9 或更高版本不兼容。请使用 Java 8。
|
||||
launch.advice.lwjgl_3_4_1=当前游戏版本存在已知问题,可能在游戏中发生卡顿。\nHMCL 可以 (通过 Java Agent) 自动优化游戏解决此问题。是否允许 HMCL 自动优化游戏?\n如遇到问题,你可以在“设置 → 通用”中关闭“允许 HMCL 修改游戏”选项。
|
||||
launch.advice.lwjgl_3_4_1.title=是否允许 HMCL 自动优化游戏?
|
||||
launch.advice.modded_java=部分模组可能与高版本 Java 不兼容。建议使用 Java %s 启动 Minecraft %s。
|
||||
launch.advice.modlauncher8=你所使用的 Forge 版本与当前使用的 Java 不兼容。请更新 Forge。
|
||||
launch.advice.newer_java=检测到你正在使用旧版本 Java 启动游戏,这可能导致部分模组引发游戏崩溃。建议更新至 Java 8 后再次启动。
|
||||
@@ -649,6 +655,8 @@ launcher.agreement=用户协议与免责声明
|
||||
launcher.agreement.accept=同意
|
||||
launcher.agreement.decline=拒绝
|
||||
launcher.agreement.hint=同意本软件的用户协议与免责声明以使用本软件。
|
||||
launcher.april_fools.switch_lzh=HMCL 启动器现已支持文言文,我们诚邀你参与体验。是否要将界面语言切换至中文 (文言)?\n在切换语言后,你可以在“置設 → 貫用 → 文”中将语言切换回中文 (简体)。
|
||||
launcher.april_fools.switch_lzh.confirm=确认切换语言?\n点击“确定”后 HMCL 将自动重启并将界面语言切换至中文 (文言);点击“取消”将继续使用中文 (简体) 进入 HMCL 主页。
|
||||
launcher.background=背景图片
|
||||
launcher.background.choose=选择背景图片
|
||||
launcher.background.classic=经典
|
||||
@@ -696,6 +704,7 @@ message.info=提示
|
||||
message.success=完成
|
||||
message.unknown=未知
|
||||
message.warning=警告
|
||||
message.question=确认
|
||||
|
||||
modpack=整合包
|
||||
modpack.choose=选择要安装的游戏整合包文件
|
||||
@@ -1199,6 +1208,8 @@ settings.game.working_directory.hint=在“版本隔离”中选择“各实例
|
||||
settings.icon=游戏图标
|
||||
|
||||
settings.launcher=启动器设置
|
||||
settings.launcher.allow_auto_agent=允许 HMCL 修改游戏
|
||||
settings.launcher.allow_auto_agent.subtitle=允许 HMCL 通过附加 Java Agent 修改游戏以改善游戏体验。
|
||||
settings.launcher.appearance=外观
|
||||
settings.launcher.brightness=主题模式
|
||||
settings.launcher.brightness.auto=跟随系统设置
|
||||
@@ -1206,6 +1217,7 @@ settings.launcher.brightness.dark=深色模式
|
||||
settings.launcher.brightness.light=浅色模式
|
||||
settings.launcher.common_path.tooltip=启动器将所有游戏资源及依赖库文件存放于此集中管理。如果游戏文件夹内有现成的将不会使用公共库文件。
|
||||
settings.launcher.debug=调试
|
||||
settings.launcher.disable_april_fools=不启用愚人节功能
|
||||
settings.launcher.disable_auto_game_options=不自动切换游戏语言
|
||||
settings.launcher.download=下载
|
||||
settings.launcher.download.threads=线程数
|
||||
@@ -1372,6 +1384,8 @@ update.channel.nightly.hint=你正在使用 HMCL 预览版。预览版更新较
|
||||
update.channel.nightly.title=预览版提示
|
||||
update.channel.stable=稳定版
|
||||
update.checking=正在检查更新
|
||||
update.disable_auto_show_update_dialog=不自动显示更新弹窗
|
||||
update.disable_auto_show_update_dialog.subtitle=启用此选项,HMCL 将不会自动弹出更新弹窗。
|
||||
update.failed=更新失败
|
||||
update.found=发现更新
|
||||
update.newest_version=最新版本为:%s
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -354,7 +354,7 @@ all-the-mods-expert-remastered;355;;All The Mods Expert: Remastered;;
|
||||
all-the-mods-0;356;;All the Mods 0;;ATM0
|
||||
all-the-mods-fabric;357;;All the Mods Fabric;;ATMF
|
||||
the-pixelmon-modpack;358;;The Pixelmon Modpack;;
|
||||
better-mc-skyblock;359;;更好的我的世界空岛;Better MC Skyblock;BMCS
|
||||
better-mc-skyblock;359;;更好的我的世界空岛;Better Minecraft Skyblock;BMCS
|
||||
age-of-fate;360;;命运时代;Age of Fate;AOF
|
||||
minecraft-kingdoms;361;;MC Kingdoms;;
|
||||
;362;;幻想旅程;Imaginary Journey;
|
||||
@@ -364,7 +364,7 @@ valhelsia-4;365;;Valhelsia 4;;
|
||||
another-quality-modpack-3;366;;Another Quality Modpack 3;;
|
||||
nomifactory;367;;Nomifactory;;
|
||||
allusiveness;368;;Allusiveness;;
|
||||
the-way-of-reincarnation;370;;转生之路;The way of reincarnation;
|
||||
the-way-of-reincarnation;370;;转生之路;The Way of Reincarnation;
|
||||
sky-friends;371;;Sky Friends;;
|
||||
magic-pouch;372;;魔法包裹;Magic Pouch;
|
||||
magica-infinitum-iii;373;;Magica Infinitum III;;
|
||||
@@ -392,7 +392,7 @@ create-yourself;395;;Create Yourself;;
|
||||
starrynight-pray-stp;397;;星夜祈临;StarryNight Pray;StP
|
||||
rustic-waters-ii;398;;乡村水域2;Rustic Waters II;
|
||||
enigmatica6expert;399;;Enigmatica 6: Expert;;E6E
|
||||
celestial-journey;400;;Celestial Journey;;
|
||||
celestial-journey;400;;星空之旅;Celestial Journey;
|
||||
;401;;天空舞台;Skyblock Create Show;
|
||||
project-architect;402;;Project Architect;;
|
||||
greed-the-resource-3-factory;403;;Greed the Resource 3: Factory;;
|
||||
@@ -509,7 +509,7 @@ mechanical-mastery;516;;Mechanical Mastery;;MM
|
||||
escape-from-2020;517;;逃离2020;Escape From 2020;
|
||||
lunaria-expert;518;;Lunaria Expert;;
|
||||
nomi-ceu;519;;Nomifactory CEu;;Nomi-CEu
|
||||
;520;;乐事 · 起源;Delight · Origins;DO
|
||||
;520;;乐事 · 起源;Delight · Origins;DO
|
||||
tankoptimization;521;;坦坦优化;Tankoptimization;
|
||||
;522;;InfinityCraft;;IC
|
||||
pick-the-stars;523;;摘星;Pick The Stars;PTS
|
||||
@@ -612,7 +612,7 @@ eternal-night-modpacks;622;;永夜;Eternal Night;ETN
|
||||
all-the-mods-9;623;;All the Mods 9;;ATM9
|
||||
spoornpack;624;;Spoornpack;;
|
||||
;625;;起源RPG;;ORPG
|
||||
;626;;织彩钓月;Oriun Tagayasu;OTSU
|
||||
aruori;626;;织彩钓月;Aruori;
|
||||
;627;;冰河物语;;
|
||||
hommage;628;;Hommage;;
|
||||
days-to-craft;629;;Seven Days To Craft;;
|
||||
@@ -650,7 +650,7 @@ valhelsia-6;660;;Valhelsia 6;;
|
||||
all-the-mods-volcanoblock;661;;All the Mods: Volcano Block;;ATMVB
|
||||
gregtronix-revoluton;662;;Gregtronix Revolution;;GR
|
||||
;663;;空岛物语;Empty Island RPG;EIRPG
|
||||
prominence-fabric;664;;卓越1 [FABRIC];Prominence I;
|
||||
prominence-1-rpg;664;;卓越 I;Prominence I;
|
||||
multiskyblock-2;665;;模块化空岛2;MultiSkyBlock 2;MSB2
|
||||
;666;;The Minehattan Project;;
|
||||
;667;;万象天工;Millions of Heavens industries;MOHI
|
||||
@@ -701,7 +701,7 @@ create-perfect-world;711;;Create: Perfect World;;
|
||||
new-simple-mods;712;;New Simple Mods - Easy to Understand;;
|
||||
;713;;灾变录;Catastrophe Record;CR
|
||||
sky-greg;714;;Sky Greg;;
|
||||
deceasedcraft;715;;亡者世界;Deceased Craft;
|
||||
deceasedcraft;715;;亡者世界;DeceasedCraft - Urban Zombie Apocalypse;
|
||||
;716;;生欲:求生;Vivere The Zombie Apocalypse;VZA
|
||||
create-planetary;717;;Create Planetary;;
|
||||
master-mabaoguo;718;;Master mabaoguo;;
|
||||
@@ -754,7 +754,7 @@ modecube;765;;ModéCube;;
|
||||
electrictechnocraft;766;;ElectricTechnoCraft;;
|
||||
create-2-mekanism;767;;Create 2 Mekanism;;C2M
|
||||
randblock;768;;随机空岛;RandBlock;RD
|
||||
;769;;格雷科技休闲版;GregTech Leisure;GTL
|
||||
;769;;格雷科技休闲版;GregTech Leisure;GTL
|
||||
frozenopolis;770;;Frozenopolis;;
|
||||
infernal-rpg;771;;Infernal Origins;;
|
||||
;772;;天狼星;Sirius;
|
||||
@@ -866,9 +866,9 @@ create-vanilla-exploration;879;;Vanilla Create Exploration;;
|
||||
progress;880;;Progress: Create;;
|
||||
aeon-automata;881;;Aeon Automata;;AA
|
||||
;882;;被恐惧支配2:收容失效;Dominated by Fear 2 Containment Breach;DoF2
|
||||
;883;;剑拔弩张之时;;TC
|
||||
tensioncraft;883;;剑拔弩张之时;;TC
|
||||
project-architect-2;884;;Project Architect 2;;
|
||||
;885;;路网通途 / 接入铁路网络;Railway Network Access;RNA
|
||||
;885;;接入铁路网络:我的世界铁路;MTR Railway Network Access;RNA-MTR
|
||||
create-mekanized;886;;Create: Mekanized;;
|
||||
create-perfect-world-2;887;;Create: Perfect World 2;;
|
||||
create-stellar;888;;Create Stellar;;
|
||||
@@ -923,7 +923,7 @@ setgamedifficulty-reika;936;;SetGameDifficulty Reika;;
|
||||
epic-journey-gtceu;937;;Epic Journey GTCEu;;
|
||||
terrafirmacraft-leap-the-top;938;;飞渡群峦;TerraFirmaCraft:Leap The Top;TFC:LTT
|
||||
gregtech-skybound;939;;Gregtech: Skybound;;
|
||||
simple-tfc;940;;简单群峦;SimpleTFC;
|
||||
simple-tfc;940;;简单群峦;Simple TFC;
|
||||
ftb-presents-direwolf20-s14;941;;FTB Presents Direwolf20 1.21;;
|
||||
final-judgment-save-the-world;942;;Final Judgment:Save the world;;
|
||||
;943;;极端世界:重置;Extreme World Reset;EWR
|
||||
@@ -998,10 +998,10 @@ ashes-to-ashes;1012;;尘归尘;Ashes To Ashes;ATA
|
||||
create-silence-and-prosperity;1014;;机械动力:寂静与繁荣;Create Silence and Prosperity;CSP
|
||||
mif;1015;;MI:Foundation;;MIF
|
||||
c2mse2;1016;;Create 2 Mekanism: Sky Edition 2;;C2MSE2
|
||||
;1017;;耕云钓月;Kumo O Tagayasu;KOTS
|
||||
kumo-o-tagayasu;1017;;耕云钓月;Kumo O Tagayasu;KOTS
|
||||
;1018;;无限战争4;Infinity War4;INF4
|
||||
;1019;;群峦传说:工业长路;;
|
||||
;1020;;自然冒险 新生;Nature Adventure Newborn;NAB
|
||||
;1020;;自然冒险 新生;Nature Adventure Newborn;NAB
|
||||
hmbo;1021;;羽舲的基础优化;Hanekmio's Basis Optimization;
|
||||
create-star-valley;1022;;Create: Gears of History;;
|
||||
;1023;;阿尔法计划;AlphaPlan;AP
|
||||
@@ -1026,7 +1026,7 @@ compression;1041;;Compression;;
|
||||
compact-sky-easy;1042;;Compact Sky: Easy;;CSE
|
||||
;1043;;丰收与探索;Harvest and Exploration;
|
||||
;1044;;脆骨症:黯光;No Flesh Within Chest-DIM;NFWC-DIM
|
||||
;1045;;小雷的优化冒险;;
|
||||
;1045;;光陆冒险;;
|
||||
cobblemon-eternal-edition;1046;;Cobblemon Eternal;;
|
||||
;1047;;Tinkerer's Quilt;;
|
||||
gregtech-expert;1048;;GregTech Expert;;
|
||||
@@ -1044,7 +1044,7 @@ ftb-oceanblock-2;1059;;FTB OceanBlock 2;;
|
||||
;1060;;伊甸空天2;Eden Aerospace 2;EAS2
|
||||
create-infinity-mechanism;1061;;机械动力:无限构件;Create:Infinity Mechanism;CIM
|
||||
beyond-depth;1062;;Beyond Depth;;
|
||||
;1063;;枪械交界地;TACZ Amid Crucible Zone;
|
||||
;1063;;枪械交界地-旧地狱篇;;
|
||||
;1064;;真幻世界;Realfantacy World;R.F.
|
||||
minenufactory;1065;;创世工厂;Minenufactory;MF
|
||||
craft-to-exile-2;1066;;放逐之路2;Craft to Exile 2 (VR Support);CTE2
|
||||
@@ -1058,7 +1058,7 @@ sky1234;1074;;sky1234;;
|
||||
star-technology;1075;;Star Technology;;
|
||||
;1076;;Hyper World;;HW
|
||||
;1077;;妖月的魔法金属整合包;yaoyaoDream's ManaMetalMod Modpack;YMM
|
||||
techcraft-severwinter;1078;;科技工艺:严冬;TechCraft: SevereWinter;TCSW
|
||||
forgottenwinter;1078;;遗忘严冬;ForgottenWinter;FTW
|
||||
randblock2-winterize;1079;;随机空岛 2:银装素裹;RandBlock2:Winterize;
|
||||
create-the-brass-concerto;1080;;机械动力:黄铜协奏曲;Create: The Brass Concerto;CBC
|
||||
explorers-eve;1081;;Explorer's Eve;;
|
||||
@@ -1071,7 +1071,6 @@ mierno;1087;;迷厄路;Mierno;
|
||||
;1088;;最后那些天;Those last day;
|
||||
;1089;;泠月狐基础整合;LunarFox;
|
||||
roguelike-adventures-and-dungeons-3;1090;;冒险与地牢3;Roguelike Adventures and Dungeons 3;RAD3
|
||||
;1091;;羽舲界;Hanekmio Instance;HMI
|
||||
;1092;;玄理2:洗剪版;EnigTech 2 - Dishwasher's Cut;ET2DC
|
||||
wheelchair-gregtech;1093;;Wheelchair Gregtech;;
|
||||
egged-up;1094;;Egged Up;;
|
||||
@@ -1110,15 +1109,15 @@ skillet-man;1123;;平底锅侠;Skillet Man;
|
||||
create-creative-and-beyond;1127;;机械动力:创造与超越;Create Creative and Beyond;CCB
|
||||
create-delight-remake;1128;;机械动力:齿轮盛宴;Create:Delight Remake;CDR
|
||||
ftb-skies-2;1129;;FTB Skies 2;;
|
||||
;1130;;星辰时代:科技魔法;Stellar Era: Technomagic;SETM
|
||||
;1130;;星熵:灵械;Stellar Era: Technomagic;SETM
|
||||
aircrashed-engineer;1131;;坠机工程师;Aircrashed Engineer;ACE
|
||||
project-tfcreate;1132;;Project: TFCreate;;
|
||||
;1133;;落幕曲;;
|
||||
;1133;;落幕曲;Closing Song;
|
||||
;1134;;超大陆:勇气;Supercontinent:Pluck;SUPL
|
||||
create-chronicles-the-endventure;1135;;Create Chronicles: The Endventure;;
|
||||
life-in-the-village-4;1136;;Life in the village 4;;LITV4
|
||||
real-island-2-modular-craft;1137;;真实空岛2:模块化之旅;Real Island 2 - Modular Craft;RI2
|
||||
;1138;;奥术咖啡馆;;
|
||||
;1138;;奥术咖啡馆;Arcane Cofferia;
|
||||
unyielding;1139;;Unyielding;;
|
||||
;1140;;剑与王国;;SNK
|
||||
the-fungal-infection-coop;1141;;The Fungal Infection | Co-Op Base Defense;;
|
||||
@@ -1140,26 +1139,26 @@ skyrealm-descent;1157;;Skyrealm: Descent;;
|
||||
;1158;;远征:以太利亚;Expedition: Ætheria;
|
||||
;1159;;快速优化;Rapid Optimization;RO
|
||||
liminal-industries;1160;;阈限工业;Liminal Industries;
|
||||
;1161;;史诗冒险;;EA
|
||||
;1161;;史诗冒险;Epic Adventure;EA
|
||||
;1162;;根自日常;ROOTINE;
|
||||
blue-archive-x-mineraft;1163;;我的世界x蔚蓝档案;BlueArchive x Minecraft;BACraft
|
||||
;1164;;Fresh & Smooth;;
|
||||
;1165;;机动重启工程:风之旅;The Caravan of Winds;TCW
|
||||
;1166;;Furry Fandom Club Boss Rush Official Offline;;FFCBR
|
||||
immersive-ultra;1167;;极致沉浸;Immersive Ultra;
|
||||
;1168;;Polymania;;
|
||||
;1168;;Polymania!;;
|
||||
survivearchive;1169;;SurviveArchive - A Blue Archive modpack;;
|
||||
beloong;1170;;化龍;BeLoong;
|
||||
;1171;;畅游自然;;SiF
|
||||
;1172;;退休工艺;Retirement Craft;RmC
|
||||
;1173;;随机枪械乱斗;NimShade's Random Gunfight;NRG
|
||||
;1173;;溯光暗潮:云影;NS's Random Gunfight;NRG
|
||||
;1174;;雪狐的基础整合;SF Integrated Modpack;SFI
|
||||
;1175;;硬核惊变;Hardcore Surprise;HS
|
||||
society-sunlit-valley;1176;;Society: Sunlit Valley;;
|
||||
ciscos-fantasy-medieval-rpg-dragonfyre;1177;;Cisco's Fantasy Medieval RPG [Dragonfyre];;
|
||||
all-the-mods-10-sky;1178;;All the Mods 10: To the Sky;;ATM10S
|
||||
;1179;;谐伴匠途;;
|
||||
;1181;;Turbo Vulkan;;TV
|
||||
;1181;;转变思路优化;New-Path Optimization;NO
|
||||
uls-zsurvival-craft;1182;;Z-Liminex;;
|
||||
;1183;;MITE 重生 3;MITE Reborn 3;MR
|
||||
reclamation-reclaim-the-world;1184;;Reclamation - Reclaim the World!;;
|
||||
@@ -1176,11 +1175,11 @@ realm-of-destiny;1192;;生命之境;Realm of Destiny;ROD
|
||||
pvzw;1195;;植物大战僵尸:世界;Plants VS Zombies : World;PVZW
|
||||
;1196;;农场物语Forge版;FarmingTales Forge;FT-WT
|
||||
new-century-adventure;1197;;新世纪之险;New Century Adventure;NCA
|
||||
repalmon;1198;;Re帕鲁梦;;RPLM
|
||||
repalmon;1198;;Re帕鲁梦;RePalmon;RPLM
|
||||
structural-ingenuity;1199;;结构型奇思妙想;Structural Ingenuity;
|
||||
arcaneaxis-ars;1200;;秘法象限;ArcaneAxis;ARS
|
||||
gregtech-easy;1201;;格雷科技简单版;GregTech Easy;GTE
|
||||
;1202;;Creation Exploring the Road of;;CETR
|
||||
;1202;;机境交响;Creation Exploring the Road of;CETR
|
||||
multiblockful-skies;1203;;Multiblockful Skies;;
|
||||
;1204;;刀剑物语;SwordStory;SS
|
||||
;1205;;无垠征途;The Unbounded Expedition;TUE
|
||||
@@ -1189,7 +1188,7 @@ custom-battleroyale-complete;1207;;完整自定义大逃杀;Custome BattleRoyale
|
||||
paradise-of-lucky-blocks;1208;;Paradise of Lucky Blocks;;
|
||||
;1209;;极致优化;Xavier Optimized;XO
|
||||
;1210;;神秘时代4花园;Thaumcraft IV Sky Islands;
|
||||
;1211;;弥散往生;Diffusion: Soulfare;
|
||||
;1211;;弥散往生;Diffusion: Soulfare;DSF
|
||||
mana-metal-new-horizons;1212;;魔法金屬:新視野;Mana Metal: New Horizons;M3NH
|
||||
farmy-skyblock;1213;;Farmy Skyblock;;
|
||||
bloody-skyblock-modpack;1214;;Bloody Skyblock;;
|
||||
@@ -1219,8 +1218,113 @@ linggango;1237;;Linggango;;
|
||||
create-industrialized-tech;1238;;Create: Industrialized Tech;;
|
||||
;1239;;灵璟铁路都市;LingJing Train City;LJTC
|
||||
erstwhile-journey-2;1240;;往昔之旅2;Erstwhile Journey 2;EJ2
|
||||
;1241;;诡厄:使徒;;
|
||||
;1241;;诡厄:使徒;;GRAE
|
||||
;1242;;时间之书 : 星之旅行-重制版;BookofTime StarTravel;BKSL
|
||||
cook-in-skyland-elegant-feast;1243;;天空厨房:典雅盛宴;Cook in Skyland Elegant Feast;CiS2
|
||||
ftb-stoneblock-4;1244;;FTB Stoneblock 4;;SB4
|
||||
ftb-stoneblock-4;1244;;FTB StoneBlock 4;;SB4
|
||||
techevdiscovery;1245;;TechEv || Discovery;;
|
||||
g3-project;1246;;G3 project;;
|
||||
;1247;;新州;Xin Zhou;XZ
|
||||
emc-and-fe-plus3;1248;;EMC & FE +++;;E&F2
|
||||
terracraft-confluence;1249;;泰拉工艺:汇流;TerraCraft:Confluence;TCC
|
||||
;1250;;探索领域;Explore Realm;ER
|
||||
beyond-cosmo;1251;;Beyond Cosmo;;
|
||||
;1252;;闲居物语;Travel Architecture Dreams;TAD
|
||||
high-altitude-alchemy;1253;;High-Altitude Alchemy;;
|
||||
;1254;;机械动力: 界位工程师;Create Boundary Engineer;CBE
|
||||
skyopolis-evolved;1255;;Skyopolis: Evolved;;
|
||||
;1256;;IFR的1.12.2优化整合包;IFR's Forgified Optimization;IFO
|
||||
;1257;;无穷匠艺;;
|
||||
;1258;;Mersive.;;MS
|
||||
;1259;;GD656极致生电整合包;GD656 Ultimate Redstone;SD
|
||||
;1260;;登神者:天阶咏叹;The God Ascender;TGA
|
||||
;1261;;虚饰作品;Fictional;
|
||||
;1262;;无尽工坊:奇境传说;Infinity Workshop: Legends of Wonderland;IWLoW
|
||||
;1263;;大群方舟;Arknights: The Great Tide;AGT
|
||||
arcadia-rpg;1264;;Arcadia[RPG];;
|
||||
ozone-skyblock-reborn-2;1265;;Ozone Skyblock Reborn 2;;
|
||||
witarcland;1266;;维塔克兰;Witarcland;WTL
|
||||
big-globe-optimized-forge;1267;;Big Globe Optimized;;
|
||||
;1268;;GD656轻量优化打底整合包;GD656 Ultimate Optimized;GDO
|
||||
terra-firma-far-horizons;1269;;群峦传说:野望;Terra Firma Far Horizons;TFFH
|
||||
;1270;;烬蓝物语 2;BlueAsh Things II;BAT2
|
||||
anvilcraft-turbo;1271;;铁砧工艺:极速版;AnvilCraft-Turbo;ANCT
|
||||
jrs-tree-of-life;1272;;生命之树;Tree of life;
|
||||
reimagined-forge;1273;;Reimagined;;
|
||||
;1274;;MTR城市建设;MTR Urban Construction;MUC
|
||||
;1275;;Zecoar 整合包;Zecoar Modpack;ZM
|
||||
create-stranded-at-sea;1276;;Create: Stranded at Sea;;
|
||||
;1277;;胶兽异界之旅;;
|
||||
spellbound-skies;1278;;Spellbound Skies;;
|
||||
cozy-meadows;1279;;Cozy Meadows;;
|
||||
;1280;;无际旅人;EpochRealms;
|
||||
mythcrafts;1281;;MYTHCRAFT 5;;MC
|
||||
;1282;;牧场物语;Harvest Moon;
|
||||
;1283;;逃逸:寄生末日;SRP: Escape Parasite Doom;SRP:epd
|
||||
;1284;;Ice and M;;IAM
|
||||
;1285;;秋水;autumn floods;AF
|
||||
;1286;;V2O5;;
|
||||
;1287;;地牢冒险者;Dungeon Adventurer;DA
|
||||
ultramodern-optimization;1289;;超现代优化;Ultramodern optimization;UO
|
||||
reverie-foundry;1290;;遐想铸造厂;Reverie Foundry;RF
|
||||
;1291;;神秘时代4·全家桶;All the thaumcraft's mod;AllTC
|
||||
over-stars;1292;;Over Stars;;
|
||||
pandemonium-nexus-anarchic-aeon;1293;;万象枢纽:无序纪元;Pandemonium Nexus: Anarchic Aeon;PNAA
|
||||
;1294;;菌染亵渎;Fungal Infection Blasphemy;FIB
|
||||
;1295;;Ice and M:Sky;;IAMS
|
||||
;1296;;TUFF PERFORMANCE;;
|
||||
;1297;;灾厄巫咒;;
|
||||
;1298;;子弹呼啸 : 重制;Bullet Whizz : Remake;BWR
|
||||
mybiomesobeautiful;1299;;My Biomes Is So Beautiful;;
|
||||
create-capitalist;1300;;Create Capitalist;;
|
||||
diving-beyond;1301;;Diving Beyond;;
|
||||
;1302;;奇星·奥术;Mystic Star_Arcane;
|
||||
guild-adventures;1303;;Guild Adventures;;
|
||||
;1304;;艾斯爱慕:新视野;Spore and M:New Horizons;SMNH
|
||||
otherworld-dnd;1305;;Otherworld [Dungeons & Dragons];;
|
||||
;1306;;奇幻起源;Fantasy Origins;
|
||||
projects;1307;;ProjectS;;
|
||||
atheism;1308;;无神论;Atheism;
|
||||
;1309;;神之征伐;Apotheosis Conquest;
|
||||
the-adventure-of-valkyriens-2;1310;;瓦尔基里大冒险2;The Adventure of Valkyriens 2;TAOV2
|
||||
;1311;;超然多味;Various flavors of supernatural;vfos
|
||||
all-the-mons;1312;;All the Mons;;ATMons
|
||||
starskyblock;1313;;Star Sky Factory;;
|
||||
;1314;;拔刀之旅;Battou No Tabi;
|
||||
;1315;;物语;Stories;
|
||||
;1316;;圣末纪元;Holy End Era;HEE
|
||||
arcane-ascension;1317;;Arcane Ascension;;
|
||||
the-cordyceps-fungus;1318;;The Cordyceps Fungus;;
|
||||
bth-beyond-the-horizon;1319;;Beyond The Horizon;;BTH
|
||||
chosens-create-integrated;1320;;Chosen's Create: Integrated;;
|
||||
;1321;;织路;Travel Extensively;TE
|
||||
;1322;;MineZ v.无尽苦寒之地;MineZ v.I.C.E;
|
||||
;1323;;森语田园;;
|
||||
;1324;;子弹呼啸;Bullet Whizz;BW
|
||||
forgemechtrek;1325;;锻械游行;ForgeMechTrek;FMT
|
||||
;1326;;生电与优化整合包;Electricity Generation Integration Package;EGIP
|
||||
create-perfect-world-post-production;1327;;Create - Perfect World Post Production;;
|
||||
terrafirmagreg-modern;1328;;群峦格雷:现代版;TerraFirmaGreg: Modern;TFG
|
||||
nanomagic;1329;;NanoMagic;;
|
||||
;1330;;接入铁路网络:沉浸铁路;IR Railway Network Access (Immersive Railroading);RNA-IR
|
||||
biohazard-project-genesis;1331;;Biohazard: Project Genesis - The Ultimate Apocalypse;;
|
||||
;1332;;齐柏林的启示录;Apocalypse of Zeppelin;AoZ
|
||||
;1333;;遗尘往事;Story of Forgotten Land;SOFL
|
||||
farming-crossing-6-dinner-party;1334;;Farming Crossing 6: Dinner Party!;;
|
||||
;1335;;血族机械师;vampire mechanic;VM
|
||||
;1336;;维度行者:天启旅途;;
|
||||
;1337;;乌托邦探险之旅;Utopian Journey;UJ
|
||||
;1338;;统合防御·钢铁晨曦;United Defense: Steel Dawn;UDSD
|
||||
;1339;;机械动力:枪火不断;Create: Gunfire Never Stops;CGNS
|
||||
;1340;;逆转未来;Battle For The Future;BFF
|
||||
day-of-mind;1341;;DayOfMind;;DOM
|
||||
;1342;;机械动力:探险生存 2;Create Adventure Survival 2;
|
||||
;1343;;云游四海;WAYFARING;WF
|
||||
;1344;;阿卡迪亚的天启;The Apocalypse of Arcadia;TAOA
|
||||
;1345;;纯净原版・焕新;Pure Vanilla Renewal;PVR
|
||||
;1346;;命轮无章;Infinite Random;IR
|
||||
;1347;;星辉科技;StellarLightTech;STL
|
||||
dragns-o-suite-pack;1348;;DragN's O-Suite Pack;;
|
||||
;1349;;机械动力:工业与牧歌;Create: Industry and Pastoral;CIAP
|
||||
;1350;;灾厄纪行;;
|
||||
beebeeblock-the-skyhive;1351;;蜂蜂空坊;Beebeeblock: The Skyhive;BTS
|
||||
@@ -2661,6 +2661,34 @@
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-freetype:3.3.3:natives-linux": null,
|
||||
"org.lwjgl:lwjgl:3.4.1:natives-linux": {
|
||||
"name": "org.glavo.hmcl:lwjgl3-natives:3.4.1-rc1",
|
||||
"downloads": {
|
||||
"classifiers": {
|
||||
"linux-loongarch64": {
|
||||
"path": "org/glavo/hmcl/lwjgl3-natives/3.4.1-rc1/lwjgl3-natives-3.4.1-rc1-linux-loongarch64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl3-natives/3.4.1-rc1-linux-loongarch64/lwjgl3-natives-3.4.1-rc1-linux-loongarch64.jar",
|
||||
"sha1": "3fed872fc2f9929d2be25389dd312dfb48e9f89e",
|
||||
"size": 22854580
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"exclude": [
|
||||
"META-INF/"
|
||||
]
|
||||
},
|
||||
"natives": {
|
||||
"linux": "linux-loongarch64"
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-jemalloc:3.4.1:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-openal:3.4.1:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-opengl:3.4.1:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-glfw:3.4.1:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-stb:3.4.1:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-tinyfd:3.4.1:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-freetype:3.4.1:natives-linux": null,
|
||||
"net.java.dev.jna:jna:5.8.0": {
|
||||
"name": "net.java.dev.jna:jna:5.13.0",
|
||||
"downloads": {
|
||||
|
||||
@@ -33,7 +33,6 @@ import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
@@ -57,6 +56,7 @@ public final class BMCLAPIDownloadProvider implements DownloadProvider {
|
||||
private final QuiltVersionList quilt;
|
||||
private final QuiltAPIVersionList quiltApi;
|
||||
private final List<Pair<String, String>> replacement;
|
||||
private final List<Pair<String, String>> fallbackReplacement;
|
||||
|
||||
public BMCLAPIDownloadProvider(String apiRoot) {
|
||||
this.apiRoot = apiRoot;
|
||||
@@ -73,7 +73,7 @@ public final class BMCLAPIDownloadProvider implements DownloadProvider {
|
||||
this.legacyFabric = new LegacyFabricVersionList(this);
|
||||
this.legacyFabricApi = new LegacyFabricAPIVersionList(this);
|
||||
|
||||
this.replacement = Arrays.asList(
|
||||
this.replacement = List.of(
|
||||
pair("https://bmclapi2.bangbang93.com", apiRoot),
|
||||
pair("https://launchermeta.mojang.com", apiRoot),
|
||||
pair("https://piston-meta.mojang.com", apiRoot),
|
||||
@@ -94,12 +94,14 @@ public final class BMCLAPIDownloadProvider implements DownloadProvider {
|
||||
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"),
|
||||
// pair("https://cdn.modrinth.com", "https://mod.mcimirror.top"),
|
||||
// pair("https://api.curseforge.com", "https://mod.mcimirror.top/curseforge"),
|
||||
// pair("https://edge.forgecdn.net", "https://mod.mcimirror.top"),
|
||||
// pair("https://mediafilez.forgecdn.net", "https://mod.mcimirror.top")
|
||||
);
|
||||
|
||||
this.fallbackReplacement = List.of(
|
||||
// https://github.com/mcmod-info-mirror/mcim-rust-api
|
||||
pair("https://api.modrinth.com", "https://mod.mcimirror.top/modrinth"),
|
||||
pair("https://cdn.modrinth.com", "https://mod.mcimirror.top"),
|
||||
pair("https://api.curseforge.com", "https://mod.mcimirror.top/curseforge"),
|
||||
pair("https://edge.forgecdn.net", "https://mod.mcimirror.top")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -136,17 +138,38 @@ public final class BMCLAPIDownloadProvider implements DownloadProvider {
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String injectURL(String baseURL) {
|
||||
private static String injectURL(List<Pair<String, String>> replacement, String baseURL) {
|
||||
for (Pair<String, String> pair : replacement) {
|
||||
if (baseURL.startsWith(pair.getKey())) {
|
||||
return pair.getValue() + baseURL.substring(pair.getKey().length());
|
||||
}
|
||||
}
|
||||
|
||||
return baseURL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String injectURL(String baseURL) {
|
||||
return injectURL(replacement, baseURL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<URI> injectURLWithCandidates(String baseURL) {
|
||||
String injected = injectURL(replacement, baseURL);
|
||||
if (injected.equals(baseURL)) {
|
||||
String fallbackInjected = injectURL(fallbackReplacement, baseURL);
|
||||
if (fallbackInjected.equals(baseURL)) {
|
||||
return List.of(NetworkUtils.toURI(baseURL));
|
||||
} else {
|
||||
return List.of(
|
||||
NetworkUtils.toURI(baseURL),
|
||||
NetworkUtils.toURI(fallbackInjected)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return List.of(NetworkUtils.toURI(injected));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getConcurrency() {
|
||||
return Math.max(Runtime.getRuntime().availableProcessors() * 2, 6);
|
||||
|
||||
@@ -42,7 +42,7 @@ public class FabricAPIVersionList extends VersionList<FabricAPIRemoteVersion> {
|
||||
@Override
|
||||
public Task<?> refreshAsync() {
|
||||
return Task.runAsync(() -> {
|
||||
for (RemoteMod.Version modVersion : Lang.toIterable(ModrinthRemoteModRepository.MODS.getRemoteVersionsById("P7dR8mSH"))) {
|
||||
for (RemoteMod.Version modVersion : Lang.toIterable(ModrinthRemoteModRepository.MODS.getRemoteVersionsById(downloadProvider, "P7dR8mSH"))) {
|
||||
for (String gameVersion : modVersion.getGameVersions()) {
|
||||
versions.put(gameVersion, new FabricAPIRemoteVersion(gameVersion, modVersion.getVersion(), modVersion.getName(), modVersion.getDatePublished(), modVersion,
|
||||
Collections.singletonList(modVersion.getFile().getUrl())));
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ public class LegacyFabricAPIVersionList extends VersionList<LegacyFabricAPIRemot
|
||||
@Override
|
||||
public Task<?> refreshAsync() {
|
||||
return Task.runAsync(() -> {
|
||||
for (RemoteMod.Version modVersion : Lang.toIterable(ModrinthRemoteModRepository.MODS.getRemoteVersionsById("legacy-fabric-api"))) {
|
||||
for (RemoteMod.Version modVersion : Lang.toIterable(ModrinthRemoteModRepository.MODS.getRemoteVersionsById(downloadProvider, "legacy-fabric-api"))) {
|
||||
for (String gameVersion : modVersion.getGameVersions()) {
|
||||
versions.put(gameVersion, new LegacyFabricAPIRemoteVersion(gameVersion, modVersion.getVersion(), modVersion.getName(), modVersion.getDatePublished(), modVersion,
|
||||
Collections.singletonList(modVersion.getFile().getUrl())));
|
||||
|
||||
@@ -42,7 +42,7 @@ public class QuiltAPIVersionList extends VersionList<QuiltAPIRemoteVersion> {
|
||||
@Override
|
||||
public Task<?> refreshAsync() {
|
||||
return Task.runAsync(() -> {
|
||||
for (RemoteMod.Version modVersion : Lang.toIterable(ModrinthRemoteModRepository.MODS.getRemoteVersionsById("qsl"))) {
|
||||
for (RemoteMod.Version modVersion : Lang.toIterable(ModrinthRemoteModRepository.MODS.getRemoteVersionsById(downloadProvider, "qsl"))) {
|
||||
for (String gameVersion : modVersion.getGameVersions()) {
|
||||
versions.put(gameVersion, new QuiltAPIRemoteVersion(gameVersion, modVersion.getVersion(), modVersion.getName(), modVersion.getDatePublished(), modVersion,
|
||||
Collections.singletonList(modVersion.getFile().getUrl())));
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.game;
|
||||
|
||||
import com.sun.tools.attach.AttachNotSupportedException;
|
||||
import com.sun.tools.attach.VirtualMachine;
|
||||
import org.jackhuang.hmcl.java.JavaInfo;
|
||||
import org.jackhuang.hmcl.util.logging.Logger;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
@@ -128,7 +129,20 @@ public final class GameDumpGenerator {
|
||||
}
|
||||
|
||||
private static void writeDumpBodyTo(VirtualMachine vm, Writer writer) throws IOException {
|
||||
execute(vm, "Thread.print -e -l", writer);
|
||||
int vmVersion = -1;
|
||||
|
||||
try {
|
||||
if (vm.getSystemProperties().get("java.version") instanceof String javaVersion)
|
||||
vmVersion = JavaInfo.parseVersion(javaVersion);
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Failed to get VM system properties", e);
|
||||
}
|
||||
|
||||
if (vmVersion >= 11)
|
||||
execute(vm, "Thread.print -e -l", writer);
|
||||
else
|
||||
execute(vm, "Thread.print -l", writer);
|
||||
|
||||
}
|
||||
|
||||
private static VirtualMachine attachVM(String lvmid, Writer writer) throws IOException, InterruptedException {
|
||||
|
||||
@@ -17,11 +17,13 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonSerializable;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.Platform;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -35,7 +37,7 @@ public record GameJavaVersion(String component, int majorVersion) {
|
||||
public static final GameJavaVersion JAVA_16 = new GameJavaVersion("java-runtime-alpha", 16);
|
||||
public static final GameJavaVersion JAVA_8 = new GameJavaVersion("jre-legacy", 8);
|
||||
|
||||
public static final GameJavaVersion LATEST = JAVA_21;
|
||||
public static final GameJavaVersion LATEST = JAVA_25;
|
||||
|
||||
public static GameJavaVersion getMinimumJavaVersion(GameVersionNumber gameVersion) {
|
||||
if (gameVersion.compareTo("26.1") >= 0)
|
||||
@@ -51,6 +53,14 @@ public record GameJavaVersion(String component, int majorVersion) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static GameJavaVersion getCleanroomJavaVersion(String cleanroomVersion) {
|
||||
VersionNumber versionNumber = VersionNumber.asVersion(StringUtils.removeSuffix(cleanroomVersion, "-alpha"));
|
||||
if (versionNumber.compareTo("0.5.0") >= 0)
|
||||
return JAVA_25;
|
||||
else
|
||||
return JAVA_21;
|
||||
}
|
||||
|
||||
public static GameJavaVersion get(int major) {
|
||||
return switch (major) {
|
||||
case 8 -> JAVA_8;
|
||||
|
||||
@@ -41,7 +41,7 @@ public enum JavaVersionConstraint {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaRuntime java) {
|
||||
public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaRuntime java, LibraryAnalyzer analyzer) {
|
||||
GameJavaVersion minimumJavaVersion = GameJavaVersion.getMinimumJavaVersion(gameVersionNumber);
|
||||
return minimumJavaVersion == null || java.getParsedVersion() >= minimumJavaVersion.majorVersion();
|
||||
}
|
||||
@@ -57,7 +57,7 @@ public enum JavaVersionConstraint {
|
||||
}
|
||||
|
||||
@Override
|
||||
public VersionRange<VersionNumber> getJavaVersionRange(Version version) {
|
||||
public VersionRange<VersionNumber> getJavaVersionRange(Version version, LibraryAnalyzer analyzer) {
|
||||
String javaVersion;
|
||||
if (Objects.requireNonNull(version.getJavaVersion()).majorVersion() >= 9) {
|
||||
javaVersion = "" + version.getJavaVersion().majorVersion();
|
||||
@@ -108,13 +108,26 @@ public enum JavaVersionConstraint {
|
||||
&& super.appliesToVersionImpl(gameVersionNumber, version, java, analyzer);
|
||||
}
|
||||
},
|
||||
CLEANROOM_JAVA_21(true, GameVersionNumber.between("1.12.2", "1.12.999"), VersionNumber.atLeast("21")) {
|
||||
CLEANROOM(true, GameVersionNumber.between("1.12.2", "1.12.999"), VersionRange.all()) {
|
||||
@Override
|
||||
protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,
|
||||
@Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) {
|
||||
protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version, @Nullable JavaRuntime java, @Nullable LibraryAnalyzer analyzer) {
|
||||
return analyzer != null && analyzer.has(LibraryAnalyzer.LibraryType.CLEANROOM)
|
||||
&& super.appliesToVersionImpl(gameVersionNumber, version, java, analyzer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VersionRange<VersionNumber> getJavaVersionRange(Version version, LibraryAnalyzer analyzer) {
|
||||
if (analyzer == null || !analyzer.has(LibraryAnalyzer.LibraryType.CLEANROOM))
|
||||
return VersionRange.all();
|
||||
|
||||
String cleanroomVersion = analyzer.getVersion(LibraryAnalyzer.LibraryType.CLEANROOM).orElse("");
|
||||
if (cleanroomVersion.isEmpty())
|
||||
return VersionRange.all();
|
||||
else
|
||||
return VersionNumber.atLeast(
|
||||
String.valueOf(GameJavaVersion.getCleanroomJavaVersion(cleanroomVersion).majorVersion())
|
||||
);
|
||||
}
|
||||
},
|
||||
// LaunchWrapper<=1.12 will crash because LaunchWrapper assumes the system class loader is an instance of URLClassLoader (Java 8)
|
||||
LAUNCH_WRAPPER(true, GameVersionNumber.atMost("1.12.999"), VersionNumber.atMost("1.8.999")) {
|
||||
@@ -143,8 +156,8 @@ public enum JavaVersionConstraint {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaRuntime java) {
|
||||
return java.getArchitecture() != Architecture.X86_64 || super.checkJava(gameVersionNumber, version, java);
|
||||
public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaRuntime java, LibraryAnalyzer analyzer) {
|
||||
return java.getArchitecture() != Architecture.X86_64 || super.checkJava(gameVersionNumber, version, java, analyzer);
|
||||
}
|
||||
},
|
||||
// Minecraft currently does not provide official support for architectures other than x86 and x86-64.
|
||||
@@ -162,7 +175,7 @@ public enum JavaVersionConstraint {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaRuntime java) {
|
||||
public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaRuntime java, LibraryAnalyzer analyzer) {
|
||||
return java.getArchitecture().isX86();
|
||||
}
|
||||
},
|
||||
@@ -194,7 +207,7 @@ public enum JavaVersionConstraint {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaRuntime java) {
|
||||
public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaRuntime java, LibraryAnalyzer analyzer) {
|
||||
int parsedJavaVersion = java.getParsedVersion();
|
||||
if (parsedJavaVersion > 17) {
|
||||
return false;
|
||||
@@ -230,7 +243,7 @@ public enum JavaVersionConstraint {
|
||||
return gameVersionRange;
|
||||
}
|
||||
|
||||
public VersionRange<VersionNumber> getJavaVersionRange(Version version) {
|
||||
public VersionRange<VersionNumber> getJavaVersionRange(Version version, LibraryAnalyzer analyzer) {
|
||||
return javaVersionRange;
|
||||
}
|
||||
|
||||
@@ -251,15 +264,15 @@ public enum JavaVersionConstraint {
|
||||
? String.valueOf(gameJavaVersion.majorVersion())
|
||||
: "1." + gameJavaVersion.majorVersion();
|
||||
|
||||
VersionRange<VersionNumber> range = getJavaVersionRange(version);
|
||||
VersionRange<VersionNumber> range = getJavaVersionRange(version, analyzer);
|
||||
VersionNumber maximum = range.getMaximum();
|
||||
|
||||
return maximum == null || maximum.compareTo(versionNumber) >= 0;
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaRuntime java) {
|
||||
return getJavaVersionRange(version).contains(java.getVersionNumber());
|
||||
public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaRuntime java, LibraryAnalyzer analyzer) {
|
||||
return getJavaVersionRange(version, analyzer).contains(java.getVersionNumber());
|
||||
}
|
||||
|
||||
public static final List<JavaVersionConstraint> ALL = Lang.immutableListOf(values());
|
||||
|
||||
@@ -34,6 +34,7 @@ import java.nio.channels.OverlappingFileLockException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.*;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
@@ -44,10 +45,18 @@ public final class World {
|
||||
|
||||
private final Path file;
|
||||
private String fileName;
|
||||
private CompoundTag levelData;
|
||||
private Image icon;
|
||||
|
||||
private CompoundTag levelData;
|
||||
private Path levelDataPath;
|
||||
|
||||
private CompoundTag worldGenSettingsDataBackingTag; // Use for writing back to the file
|
||||
private CompoundTag normalizedWorldGenSettingsData; // Use for reading/modification
|
||||
private Path worldGenSettingsDataPath;
|
||||
|
||||
private CompoundTag playerData; // Use for both reading/modification and writing back to the file
|
||||
private Path playerDataPath;
|
||||
|
||||
public World(Path file) throws IOException {
|
||||
this.file = file;
|
||||
|
||||
@@ -76,14 +85,10 @@ public final class World {
|
||||
public void setWorldName(String worldName) throws IOException {
|
||||
if (levelData.get("Data") instanceof CompoundTag data && data.get("LevelName") instanceof StringTag levelNameTag) {
|
||||
levelNameTag.setValue(worldName);
|
||||
writeLevelDat(levelData);
|
||||
writeLevelData();
|
||||
}
|
||||
}
|
||||
|
||||
public Path getLevelDatFile() {
|
||||
return file.resolve("level.dat");
|
||||
}
|
||||
|
||||
public Path getSessionLockFile() {
|
||||
return file.resolve("session.lock");
|
||||
}
|
||||
@@ -92,6 +97,14 @@ public final class World {
|
||||
return levelData;
|
||||
}
|
||||
|
||||
public @Nullable CompoundTag getNormalizedWorldGenSettingsData() {
|
||||
return normalizedWorldGenSettingsData;
|
||||
}
|
||||
|
||||
public @Nullable CompoundTag getPlayerData() {
|
||||
return playerData;
|
||||
}
|
||||
|
||||
public long getLastPlayed() {
|
||||
CompoundTag data = levelData.get("Data");
|
||||
LongTag lastPlayedTag = data.get("LastPlayed");
|
||||
@@ -108,10 +121,12 @@ public final class World {
|
||||
}
|
||||
|
||||
public @Nullable Long getSeed() {
|
||||
CompoundTag data = levelData.get("Data");
|
||||
if (data.get("WorldGenSettings") instanceof CompoundTag worldGenSettingsTag && worldGenSettingsTag.get("seed") instanceof LongTag seedTag) { //Valid after 1.16
|
||||
// Valid after 1.16(20w20a)
|
||||
if (normalizedWorldGenSettingsData != null && normalizedWorldGenSettingsData.get("seed") instanceof LongTag seedTag) {
|
||||
return seedTag.getValue();
|
||||
} else if (data.get("RandomSeed") instanceof LongTag seedTag) { //Valid before 1.16
|
||||
}
|
||||
// Valid before 1.16(20w20a)
|
||||
if (levelData.get("Data") instanceof CompoundTag data && data.get("RandomSeed") instanceof LongTag seedTag) {
|
||||
return seedTag.getValue();
|
||||
}
|
||||
return null;
|
||||
@@ -119,22 +134,28 @@ public final class World {
|
||||
|
||||
public boolean isLargeBiomes() {
|
||||
CompoundTag data = levelData.get("Data");
|
||||
if (data.get("generatorName") instanceof StringTag generatorNameTag) { //Valid before 1.16
|
||||
|
||||
// Valid before 1.16(20w20a)
|
||||
if (data.get("generatorName") instanceof StringTag generatorNameTag) {
|
||||
return "largeBiomes".equals(generatorNameTag.getValue());
|
||||
} else {
|
||||
if (data.get("WorldGenSettings") instanceof CompoundTag worldGenSettingsTag
|
||||
&& worldGenSettingsTag.get("dimensions") instanceof CompoundTag dimensionsTag
|
||||
&& dimensionsTag.get("minecraft:overworld") instanceof CompoundTag overworldTag
|
||||
}
|
||||
// Unified handling of logic after version 1.16
|
||||
else if (normalizedWorldGenSettingsData != null
|
||||
&& normalizedWorldGenSettingsData.get("dimensions") instanceof CompoundTag dimensionsTag) {
|
||||
if (dimensionsTag.get("minecraft:overworld") instanceof CompoundTag overworldTag
|
||||
&& overworldTag.get("generator") instanceof CompoundTag generatorTag) {
|
||||
// Valid between 1.16(20w20a) and 1.18(21w37a)
|
||||
if (generatorTag.get("biome_source") instanceof CompoundTag biomeSourceTag
|
||||
&& biomeSourceTag.get("large_biomes") instanceof ByteTag largeBiomesTag) { //Valid between 1.16 and 1.16.2
|
||||
&& biomeSourceTag.get("large_biomes") instanceof ByteTag largeBiomesTag) {
|
||||
return largeBiomesTag.getValue() == (byte) 1;
|
||||
} else if (generatorTag.get("settings") instanceof StringTag settingsTag) { //Valid after 1.16.2
|
||||
}
|
||||
// Valid after 1.18(21w37a)
|
||||
else if (generatorTag.get("settings") instanceof StringTag settingsTag) {
|
||||
return "minecraft:large_biomes".equals(settingsTag.getValue());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Image getIcon() {
|
||||
@@ -166,8 +187,8 @@ public final class World {
|
||||
if (!Files.exists(levelDat)) {
|
||||
throw new IOException("Not a valid world directory since level.dat or special_level.dat cannot be found.");
|
||||
}
|
||||
loadAndCheckLevelDat(levelDat);
|
||||
this.levelDataPath = levelDat;
|
||||
loadAndCheckWorldData();
|
||||
|
||||
Path iconFile = file.resolve("icon.png");
|
||||
if (Files.isRegularFile(iconFile)) {
|
||||
@@ -189,7 +210,7 @@ public final class World {
|
||||
if (!Files.exists(levelDat)) {
|
||||
throw new IOException("Not a valid world zip file since level.dat or special_level.dat cannot be found.");
|
||||
}
|
||||
loadAndCheckLevelDat(levelDat);
|
||||
loadAndCheckLevelData(levelDat);
|
||||
|
||||
Path iconFile = root.resolve("icon.png");
|
||||
if (Files.isRegularFile(iconFile)) {
|
||||
@@ -221,8 +242,13 @@ public final class World {
|
||||
}
|
||||
}
|
||||
|
||||
private void loadAndCheckLevelDat(Path levelDat) throws IOException {
|
||||
this.levelData = parseLevelDat(levelDat);
|
||||
private void loadAndCheckWorldData() throws IOException {
|
||||
loadAndCheckLevelData(levelDataPath);
|
||||
loadOtherData();
|
||||
}
|
||||
|
||||
private void loadAndCheckLevelData(Path levelDat) throws IOException {
|
||||
this.levelData = readTag(levelDat);
|
||||
CompoundTag data = levelData.get("Data");
|
||||
if (data == null)
|
||||
throw new IOException("level.dat missing Data");
|
||||
@@ -234,10 +260,54 @@ public final class World {
|
||||
throw new IOException("level.dat missing LastPlayed");
|
||||
}
|
||||
|
||||
public void reloadLevelDat() throws IOException {
|
||||
if (levelDataPath != null) {
|
||||
loadAndCheckLevelDat(this.levelDataPath);
|
||||
private void loadOtherData() throws IOException {
|
||||
if (!(levelData.get("Data") instanceof CompoundTag data)) return;
|
||||
|
||||
Path worldGenSettingsDatPath = file.resolve("data/minecraft/world_gen_settings.dat");
|
||||
if (data.get("WorldGenSettings") instanceof CompoundTag worldGenSettingsTag) {
|
||||
setWorldGenSettingsData(null, worldGenSettingsTag, worldGenSettingsTag);
|
||||
} else if (Files.isRegularFile(worldGenSettingsDatPath)) {
|
||||
CompoundTag raw = readTag(worldGenSettingsDatPath);
|
||||
if (raw.get("data") instanceof CompoundTag compoundTag) {
|
||||
setWorldGenSettingsData(worldGenSettingsDatPath, raw, compoundTag);
|
||||
} else {
|
||||
setWorldGenSettingsData(null, null, null);
|
||||
}
|
||||
} else {
|
||||
setWorldGenSettingsData(null, null, null);
|
||||
}
|
||||
|
||||
if (data.get("Player") instanceof CompoundTag playerTag) {
|
||||
setPlayerData(null, playerTag);
|
||||
} else if (data.get("singleplayer_uuid") instanceof IntArrayTag uuidTag && uuidTag.getValue().length == 4) {
|
||||
int[] uuidValue = uuidTag.getValue();
|
||||
long mostSigBits = ((long) uuidValue[0] << 32) | (uuidValue[1] & 0xFFFFFFFFL);
|
||||
long leastSigBits = ((long) uuidValue[2] << 32) | (uuidValue[3] & 0xFFFFFFFFL);
|
||||
String playerUUID = new UUID(mostSigBits, leastSigBits).toString();
|
||||
Path playerDatPath = file.resolve("players/data/" + playerUUID + ".dat");
|
||||
if (Files.exists(playerDatPath)) {
|
||||
setPlayerData(playerDatPath, readTag(playerDatPath));
|
||||
} else {
|
||||
setPlayerData(null, null);
|
||||
}
|
||||
} else {
|
||||
setPlayerData(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void setWorldGenSettingsData(Path worldGenSettingsDataPath, CompoundTag worldGenSettingsDataBackingTag, CompoundTag unifiedWorldGenSettingsData) {
|
||||
this.worldGenSettingsDataPath = worldGenSettingsDataPath;
|
||||
this.worldGenSettingsDataBackingTag = worldGenSettingsDataBackingTag;
|
||||
this.normalizedWorldGenSettingsData = unifiedWorldGenSettingsData;
|
||||
}
|
||||
|
||||
private void setPlayerData(Path playerDataPath, CompoundTag playerData) {
|
||||
this.playerDataPath = playerDataPath;
|
||||
this.playerData = playerData;
|
||||
}
|
||||
|
||||
public void reloadWorldData() throws IOException {
|
||||
loadAndCheckWorldData();
|
||||
}
|
||||
|
||||
// The rename method is used to rename temporary world object during installation and copying,
|
||||
@@ -249,7 +319,7 @@ public final class World {
|
||||
// Change the name recorded in level.dat
|
||||
CompoundTag data = levelData.get("Data");
|
||||
data.put(new StringTag("LevelName", newName));
|
||||
writeLevelDat(levelData);
|
||||
writeLevelData();
|
||||
|
||||
// then change the folder's name
|
||||
Files.move(file, file.resolveSibling(newName));
|
||||
@@ -345,27 +415,41 @@ public final class World {
|
||||
}
|
||||
}
|
||||
|
||||
public void writeLevelDat(CompoundTag nbt) throws IOException {
|
||||
if (!Files.isDirectory(file))
|
||||
throw new IOException("Not a valid world directory");
|
||||
public void writeWorldData() throws IOException {
|
||||
if (!Files.isDirectory(file)) throw new IOException("Not a valid world directory");
|
||||
|
||||
FileUtils.saveSafely(getLevelDatFile(), os -> {
|
||||
writeLevelData();
|
||||
|
||||
if (worldGenSettingsDataPath != null && worldGenSettingsDataBackingTag != null) {
|
||||
writeTag(worldGenSettingsDataBackingTag, worldGenSettingsDataPath);
|
||||
}
|
||||
|
||||
if (playerDataPath != null && playerData != null) {
|
||||
writeTag(playerData, playerDataPath);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeLevelData() throws IOException {
|
||||
writeTag(levelData, levelDataPath);
|
||||
}
|
||||
|
||||
private CompoundTag readTag(Path path) throws IOException {
|
||||
try (InputStream is = new GZIPInputStream(Files.newInputStream(path))) {
|
||||
Tag tag = NBTIO.readTag(is);
|
||||
if (tag instanceof CompoundTag compoundTag) return compoundTag;
|
||||
throw new IOException("NBT file malformed: " + path);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeTag(CompoundTag nbt, Path path) throws IOException {
|
||||
if (!Files.isDirectory(file)) throw new IOException("Not a valid world directory");
|
||||
FileUtils.saveSafely(path, os -> {
|
||||
try (OutputStream gos = new GZIPOutputStream(os)) {
|
||||
NBTIO.writeTag(gos, nbt);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static CompoundTag parseLevelDat(Path path) throws IOException {
|
||||
try (InputStream is = new GZIPInputStream(Files.newInputStream(path))) {
|
||||
Tag nbt = NBTIO.readTag(is);
|
||||
if (nbt instanceof CompoundTag compoundTag)
|
||||
return compoundTag;
|
||||
else
|
||||
throw new IOException("level.dat malformed");
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isLocked(Path sessionLockFile) {
|
||||
try (FileChannel fileChannel = FileChannel.open(sessionLockFile, StandardOpenOption.WRITE)) {
|
||||
return fileChannel.tryLock() == null;
|
||||
@@ -382,14 +466,17 @@ public final class World {
|
||||
public static List<World> getWorlds(Path savesDir) {
|
||||
if (Files.exists(savesDir)) {
|
||||
try (Stream<Path> stream = Files.list(savesDir)) {
|
||||
return stream.flatMap(world -> {
|
||||
try {
|
||||
return Stream.of(new World(world.toAbsolutePath().normalize()));
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to read world " + world, e);
|
||||
return Stream.empty();
|
||||
}
|
||||
}).toList();
|
||||
return stream
|
||||
.filter(Files::isDirectory)
|
||||
.flatMap(world -> {
|
||||
try {
|
||||
return Stream.of(new World(world.toAbsolutePath().normalize()));
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to read world " + world, e);
|
||||
return Stream.empty();
|
||||
}
|
||||
})
|
||||
.toList();
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to read saves", e);
|
||||
}
|
||||
|
||||
@@ -650,8 +650,10 @@ public class DefaultLauncher extends Launcher {
|
||||
final String command = usePowerShell ? null : commandLine.commandLine.toString();
|
||||
Map<String, String> envVars = getEnvVars();
|
||||
|
||||
if (!usePowerShell && isWindows) {
|
||||
if (command.length() > 8192) { // maximum length of the command in cmd
|
||||
if (isWindows && !usePowerShell) {
|
||||
// https://stackoverflow.com/a/28452546
|
||||
// https://learn.microsoft.com/troubleshoot/windows-client/shell-experience/command-line-string-limitation
|
||||
if (command.length() > 32767) {
|
||||
throw new CommandTooLongException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.mod;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -177,10 +178,10 @@ public final class LocalModFile implements Comparable<LocalModFile> {
|
||||
file = modManager.disableMod(file);
|
||||
}
|
||||
|
||||
public ModUpdate checkUpdates(String gameVersion, RemoteModRepository repository) throws IOException {
|
||||
public ModUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteModRepository repository) throws IOException {
|
||||
Optional<RemoteMod.Version> currentVersion = repository.getRemoteVersionByLocalFile(this, file);
|
||||
if (!currentVersion.isPresent()) return null;
|
||||
List<RemoteMod.Version> remoteVersions = repository.getRemoteVersionsById(currentVersion.get().getModid())
|
||||
List<RemoteMod.Version> remoteVersions = repository.getRemoteVersionsById(downloadProvider, currentVersion.get().getModid())
|
||||
.filter(version -> version.getGameVersions().contains(gameVersion))
|
||||
.filter(version -> version.getLoaders().contains(getModLoaderType()))
|
||||
.filter(version -> version.getDatePublished().compareTo(currentVersion.get().getDatePublished()) > 0)
|
||||
|
||||
@@ -210,6 +210,7 @@ public class ModpackExportInfo {
|
||||
}
|
||||
|
||||
public static class Options {
|
||||
private boolean requireAuthor;
|
||||
private boolean requireUrl;
|
||||
private boolean requireForceUpdate;
|
||||
private boolean requireFileApi;
|
||||
@@ -225,6 +226,10 @@ public class ModpackExportInfo {
|
||||
public Options() {
|
||||
}
|
||||
|
||||
public boolean isRequireAuthor() {
|
||||
return requireAuthor;
|
||||
}
|
||||
|
||||
public boolean isRequireUrl() {
|
||||
return requireUrl;
|
||||
}
|
||||
@@ -269,6 +274,11 @@ public class ModpackExportInfo {
|
||||
return requireSkipCurseForgeRemoteFiles;
|
||||
}
|
||||
|
||||
public Options requireAuthor() {
|
||||
requireAuthor = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Options requireUrl() {
|
||||
requireUrl = true;
|
||||
return this;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.mod;
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository;
|
||||
import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
@@ -32,12 +33,12 @@ public final class RemoteMod {
|
||||
|
||||
public static final RemoteMod BROKEN = new RemoteMod("", "", "RemoteMod.BROKEN", "", Collections.emptyList(), "", "", new RemoteMod.IMod() {
|
||||
@Override
|
||||
public List<RemoteMod> loadDependencies(RemoteModRepository modRepository) throws IOException {
|
||||
public List<RemoteMod> loadDependencies(RemoteModRepository modRepository, DownloadProvider downloadProvider) throws IOException {
|
||||
throw new IOException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<RemoteMod.Version> loadVersions(RemoteModRepository modRepository) throws IOException {
|
||||
public Stream<RemoteMod.Version> loadVersions(RemoteModRepository modRepository, DownloadProvider downloadProvider) throws IOException {
|
||||
throw new IOException();
|
||||
}
|
||||
});
|
||||
@@ -154,12 +155,12 @@ public final class RemoteMod {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public RemoteMod load() throws IOException {
|
||||
public RemoteMod load(DownloadProvider downloadProvider) throws IOException {
|
||||
if (this.remoteMod == null) {
|
||||
if (this.type == DependencyType.BROKEN) {
|
||||
this.remoteMod = RemoteMod.BROKEN;
|
||||
} else {
|
||||
this.remoteMod = this.remoteModRepository.resolveDependency(this.id);
|
||||
this.remoteMod = this.remoteModRepository.resolveDependency(downloadProvider, this.id);
|
||||
}
|
||||
}
|
||||
return this.remoteMod;
|
||||
@@ -202,9 +203,9 @@ public final class RemoteMod {
|
||||
}
|
||||
|
||||
public interface IMod {
|
||||
List<RemoteMod> loadDependencies(RemoteModRepository modRepository) throws IOException;
|
||||
List<RemoteMod> loadDependencies(RemoteModRepository modRepository, DownloadProvider downloadProvider) throws IOException;
|
||||
|
||||
Stream<Version> loadVersions(RemoteModRepository modRepository) throws IOException;
|
||||
Stream<Version> loadVersions(RemoteModRepository modRepository, DownloadProvider downloadProvider) throws IOException;
|
||||
}
|
||||
|
||||
public interface IVersion {
|
||||
|
||||
Loaded 100 of 136 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user