Compare commits
28
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
578aaafb0a | ||
|
|
60deea64c8 | ||
|
|
7e1cd4319b | ||
|
|
318da840f9 | ||
|
|
0c7046d764 | ||
|
|
bdf4886445 | ||
|
|
4a758d045d | ||
|
|
1059a4b3d4 | ||
|
|
a25eb14da4 | ||
|
|
349b663c83 | ||
|
|
04781da3a7 | ||
|
|
96e1ddff79 | ||
|
|
e8502890a4 | ||
|
|
4d0619a9df | ||
|
|
fba14737f9 | ||
|
|
5de98399c5 | ||
|
|
7adfa0e3d1 | ||
|
|
a45b636b1c | ||
|
|
50c22e1f43 | ||
|
|
3b38c22398 | ||
|
|
618243350e | ||
|
|
ff4940e79f | ||
|
|
1c163934ee | ||
|
|
c513d00c50 | ||
|
|
2c9549b36b | ||
|
|
3389e0a934 | ||
|
|
78149449a9 | ||
|
|
b792b23ad4 |
No files matched your search
@@ -54,6 +54,7 @@ public final class Main {
|
||||
public static void main(String[] args) {
|
||||
System.getProperties().putIfAbsent("java.net.useSystemProxies", "true");
|
||||
System.getProperties().putIfAbsent("javafx.autoproxy.disable", "true");
|
||||
System.getProperties().putIfAbsent("javafx.animation.pulse", "120");
|
||||
System.getProperties().putIfAbsent("http.agent", "HMCL/" + Metadata.VERSION);
|
||||
|
||||
LOG.start(Metadata.HMCL_DIRECTORY.resolve("logs"));
|
||||
|
||||
@@ -24,7 +24,9 @@ import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Font;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.Lazy;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
@@ -57,29 +59,41 @@ public class Theme {
|
||||
Color.web("#B71C1C") // red
|
||||
};
|
||||
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
private static Optional<Font> font;
|
||||
private static final Lazy<Font> FONT = new Lazy<>(() -> {
|
||||
String[] fileNames = {"font.ttf", "font.otf", "font.woff"};
|
||||
|
||||
private static Optional<Font> tryLoadFont() {
|
||||
//noinspection OptionalAssignedToNull
|
||||
if (font != null) {
|
||||
return font;
|
||||
}
|
||||
for (String fileName : fileNames) {
|
||||
Path path = Paths.get(fileName).toAbsolutePath();
|
||||
if (Files.isRegularFile(path)) {
|
||||
try {
|
||||
Font font = Font.loadFont(path.toUri().toURL().toExternalForm(), 0);
|
||||
if (font != null) {
|
||||
return font;
|
||||
}
|
||||
} catch (MalformedURLException ignored) {
|
||||
}
|
||||
|
||||
Path path = Paths.get("font.ttf");
|
||||
if (!Files.isRegularFile(path)) {
|
||||
path = Paths.get("font.otf");
|
||||
}
|
||||
|
||||
if (Files.isRegularFile(path)) {
|
||||
try {
|
||||
return font = Optional.ofNullable(Font.loadFont(path.toAbsolutePath().toUri().toURL().toExternalForm(), 0));
|
||||
} catch (MalformedURLException ignored) {
|
||||
LOG.warning("Failed to load font " + path);
|
||||
}
|
||||
}
|
||||
|
||||
return font = Optional.empty();
|
||||
}
|
||||
for (String fileName : fileNames) {
|
||||
Path path = Metadata.HMCL_DIRECTORY.resolve(fileName);
|
||||
if (Files.isRegularFile(path)) {
|
||||
try {
|
||||
Font font = Font.loadFont(path.toUri().toURL().toExternalForm(), 0);
|
||||
if (font != null) {
|
||||
return font;
|
||||
}
|
||||
} catch (MalformedURLException ignored) {
|
||||
}
|
||||
|
||||
LOG.warning("Failed to load font " + path);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
public static Theme getTheme() {
|
||||
Theme theme = config().getTheme();
|
||||
@@ -172,10 +186,10 @@ public class Theme {
|
||||
|
||||
Pair<String, String> fontStyle = null;
|
||||
if (fontFamily == null) {
|
||||
Optional<Font> font = tryLoadFont();
|
||||
if (font.isPresent()) {
|
||||
fontFamily = font.get().getFamily();
|
||||
fontStyle = parseFontStyle(font.get().getStyle());
|
||||
Font font = FONT.get();
|
||||
if (font != null) {
|
||||
fontFamily = font.getFamily();
|
||||
fontStyle = parseFontStyle(font.getStyle());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,9 @@ package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXDialogLayout;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.WeakInvalidationListener;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
@@ -34,6 +37,7 @@ import javafx.scene.paint.Color;
|
||||
import javafx.stage.Screen;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
@@ -42,6 +46,7 @@ import org.jackhuang.hmcl.setting.*;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.account.AccountListPage;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorController;
|
||||
@@ -267,6 +272,24 @@ public final class Controllers {
|
||||
stage.initStyle(StageStyle.TRANSPARENT);
|
||||
stage.setScene(scene);
|
||||
|
||||
if (AnimationUtils.playWindowAnimation()) {
|
||||
Timeline timeline = new Timeline(
|
||||
new KeyFrame(Duration.millis(0),
|
||||
new KeyValue(decorator.getDecorator().opacityProperty(), 0, FXUtils.EASE),
|
||||
new KeyValue(decorator.getDecorator().scaleXProperty(), 0.8, FXUtils.EASE),
|
||||
new KeyValue(decorator.getDecorator().scaleYProperty(), 0.8, FXUtils.EASE),
|
||||
new KeyValue(decorator.getDecorator().scaleZProperty(), 0.8, FXUtils.EASE)
|
||||
),
|
||||
new KeyFrame(Duration.millis(600),
|
||||
new KeyValue(decorator.getDecorator().opacityProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(decorator.getDecorator().scaleXProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(decorator.getDecorator().scaleYProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(decorator.getDecorator().scaleZProperty(), 1, FXUtils.EASE)
|
||||
)
|
||||
);
|
||||
timeline.play();
|
||||
}
|
||||
|
||||
if (!Architecture.SYSTEM_ARCH.isX86() && globalConfig().getPlatformPromptVersion() < 1) {
|
||||
Runnable continueAction = () -> globalConfig().setPlatformPromptVersion(1);
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ import javafx.beans.value.*;
|
||||
import javafx.event.Event;
|
||||
import javafx.event.EventDispatcher;
|
||||
import javafx.event.EventType;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
@@ -255,20 +254,6 @@ public final class FXUtils {
|
||||
});
|
||||
}
|
||||
|
||||
public static <K, T> void setupCellValueFactory(JFXTreeTableColumn<K, T> column, Function<K, ObservableValue<T>> mapper) {
|
||||
column.setCellValueFactory(param -> {
|
||||
if (column.validateValue(param))
|
||||
return mapper.apply(param.getValue().getValue());
|
||||
else
|
||||
return column.getComputedValue(param);
|
||||
});
|
||||
}
|
||||
|
||||
public static Node wrapMargin(Node node, Insets insets) {
|
||||
StackPane.setMargin(node, insets);
|
||||
return new StackPane(node);
|
||||
}
|
||||
|
||||
public static void setValidateWhileTextChanged(Node field, boolean validate) {
|
||||
if (field instanceof JFXTextField) {
|
||||
if (validate) {
|
||||
@@ -622,22 +607,6 @@ public final class FXUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void bindBoolean(JFXToggleButton toggleButton, Property<Boolean> property) {
|
||||
toggleButton.selectedProperty().bindBidirectional(property);
|
||||
}
|
||||
|
||||
public static void unbindBoolean(JFXToggleButton toggleButton, Property<Boolean> property) {
|
||||
toggleButton.selectedProperty().unbindBidirectional(property);
|
||||
}
|
||||
|
||||
public static void bindBoolean(JFXCheckBox checkBox, Property<Boolean> property) {
|
||||
checkBox.selectedProperty().bindBidirectional(property);
|
||||
}
|
||||
|
||||
public static void unbindBoolean(JFXCheckBox checkBox, Property<Boolean> property) {
|
||||
checkBox.selectedProperty().unbindBidirectional(property);
|
||||
}
|
||||
|
||||
private static final class EnumBidirectionalBinding<E extends Enum<E>> implements InvalidationListener, WeakListener {
|
||||
private final WeakReference<JFXComboBox<E>> comboBoxRef;
|
||||
private final WeakReference<Property<E>> propertyRef;
|
||||
@@ -1085,6 +1054,8 @@ public final class FXUtils {
|
||||
}
|
||||
};
|
||||
|
||||
public static final Interpolator EASE = Interpolator.SPLINE(0.25, 0.1, 0.25, 1);
|
||||
|
||||
public static void onEscPressed(Node node, Runnable action) {
|
||||
node.addEventHandler(KeyEvent.KEY_PRESSED, e -> {
|
||||
if (e.getCode() == KeyCode.ESCAPE) {
|
||||
@@ -1103,6 +1074,18 @@ public final class FXUtils {
|
||||
});
|
||||
}
|
||||
|
||||
public static void copyOnDoubleClick(Labeled label) {
|
||||
label.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> {
|
||||
if (e.getButton() == MouseButton.PRIMARY && e.getClickCount() == 2) {
|
||||
String text = label.getText();
|
||||
if (text != null && !text.isEmpty()) {
|
||||
copyText(label.getText());
|
||||
e.consume();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void copyText(String text) {
|
||||
ClipboardContent content = new ClipboardContent();
|
||||
content.putString(text);
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXRadioButton;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.canvas.Canvas;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.SkinBase;
|
||||
@@ -50,13 +51,11 @@ public final class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
super(skinnable);
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
root.setCursor(Cursor.HAND);
|
||||
FXUtils.onClicked(root, skinnable::fire);
|
||||
|
||||
JFXRadioButton chkSelected = new JFXRadioButton() {
|
||||
@Override
|
||||
public void fire() {
|
||||
skinnable.fire();
|
||||
}
|
||||
};
|
||||
JFXRadioButton chkSelected = new JFXRadioButton();
|
||||
chkSelected.setMouseTransparent(true);
|
||||
BorderPane.setAlignment(chkSelected, Pos.CENTER);
|
||||
chkSelected.selectedProperty().bind(skinnable.selectedProperty());
|
||||
root.setLeft(chkSelected);
|
||||
|
||||
@@ -101,6 +101,9 @@ public class OfflineAccountSkinPane extends StackPane {
|
||||
VBox optionPane = new VBox(skinItem, skinOptionPane);
|
||||
pane.setRight(optionPane);
|
||||
|
||||
skinSelector.maxWidthProperty().bind(skinOptionPane.maxWidthProperty().multiply(0.7));
|
||||
capeSelector.maxWidthProperty().bind(skinOptionPane.maxWidthProperty().multiply(0.7));
|
||||
|
||||
layout.setBody(pane);
|
||||
|
||||
cslApiField.setPromptText(i18n("account.skin.type.csl_api.location.hint"));
|
||||
|
||||
@@ -1,7 +1,28 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.animation;
|
||||
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class AnimationUtils {
|
||||
|
||||
private AnimationUtils() {
|
||||
@@ -15,9 +36,15 @@ public final class AnimationUtils {
|
||||
public static void init() {
|
||||
}
|
||||
|
||||
private static final boolean enabled = !ConfigHolder.config().isAnimationDisabled();
|
||||
private static final boolean ENABLED = !ConfigHolder.config().isAnimationDisabled();
|
||||
private static final boolean PLAY_WINDOW_ANIMATION = ENABLED
|
||||
&& (!OperatingSystem.CURRENT_OS.isLinuxOrBSD() || "DDE".equals(System.getenv("XDG_CURRENT_DESKTOP")));
|
||||
|
||||
public static boolean isAnimationEnabled() {
|
||||
return enabled;
|
||||
return ENABLED;
|
||||
}
|
||||
|
||||
public static boolean playWindowAnimation() {
|
||||
return PLAY_WINDOW_ANIMATION;
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.animation;
|
||||
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.Node;
|
||||
@@ -26,6 +25,8 @@ import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
|
||||
public class TransitionPane extends StackPane implements AnimationHandler {
|
||||
private static final Duration DEFAULT_DURATION = Duration.millis(200);
|
||||
|
||||
private Duration duration;
|
||||
private Node previousNode, currentNode;
|
||||
|
||||
@@ -55,7 +56,7 @@ public class TransitionPane extends StackPane implements AnimationHandler {
|
||||
}
|
||||
|
||||
public void setContent(Node newView, AnimationProducer transition) {
|
||||
setContent(newView, transition, Duration.millis(160));
|
||||
setContent(newView, transition, DEFAULT_DURATION);
|
||||
}
|
||||
|
||||
public void setContent(Node newView, AnimationProducer transition, Duration duration) {
|
||||
@@ -64,26 +65,25 @@ public class TransitionPane extends StackPane implements AnimationHandler {
|
||||
updateContent(newView);
|
||||
|
||||
if (previousNode == EMPTY_PANE) {
|
||||
setMouseTransparent(false);
|
||||
getChildren().setAll(newView);
|
||||
return;
|
||||
}
|
||||
|
||||
if (AnimationUtils.isAnimationEnabled()) {
|
||||
if (AnimationUtils.isAnimationEnabled() && transition != ContainerAnimations.NONE) {
|
||||
setMouseTransparent(true);
|
||||
transition.init(this);
|
||||
|
||||
// runLater or "init" will not work
|
||||
Platform.runLater(() -> {
|
||||
Timeline newAnimation = new Timeline();
|
||||
newAnimation.getKeyFrames().addAll(transition.animate(this));
|
||||
newAnimation.getKeyFrames().add(new KeyFrame(duration, e -> {
|
||||
newAnimation.getKeyFrames().setAll(transition.animate(this));
|
||||
newAnimation.setOnFinished(e -> {
|
||||
setMouseTransparent(false);
|
||||
getChildren().remove(previousNode);
|
||||
}));
|
||||
});
|
||||
FXUtils.playAnimation(this, "transition_pane", newAnimation);
|
||||
});
|
||||
} else {
|
||||
setMouseTransparent(false);
|
||||
getChildren().remove(previousNode);
|
||||
}
|
||||
}
|
||||
@@ -99,8 +99,6 @@ public class TransitionPane extends StackPane implements AnimationHandler {
|
||||
if (previousNode == newView)
|
||||
previousNode = EMPTY_PANE;
|
||||
|
||||
setMouseTransparent(true);
|
||||
|
||||
currentNode = newView;
|
||||
|
||||
getChildren().setAll(previousNode, currentNode);
|
||||
|
||||
+10
-6
@@ -20,24 +20,28 @@ package org.jackhuang.hmcl.ui.construct;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Optional;
|
||||
import java.util.Stack;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public class StackContainerPane extends StackPane {
|
||||
private final Stack<Node> stack = new Stack<>();
|
||||
public class JFXDialogPane extends StackPane {
|
||||
private final ArrayList<Node> stack = new ArrayList<>();
|
||||
|
||||
public int size() {
|
||||
return stack.size();
|
||||
}
|
||||
|
||||
public Optional<Node> peek() {
|
||||
if (stack.isEmpty()) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return Optional.of(stack.peek());
|
||||
return Optional.of(stack.get(stack.size() - 1));
|
||||
}
|
||||
}
|
||||
|
||||
public void push(Node node) {
|
||||
stack.push(node);
|
||||
stack.add(node);
|
||||
getChildren().setAll(node);
|
||||
|
||||
LOG.info(this + " " + stack);
|
||||
@@ -48,7 +52,7 @@ public class StackContainerPane extends StackPane {
|
||||
if (stack.isEmpty())
|
||||
getChildren().setAll();
|
||||
else
|
||||
getChildren().setAll(stack.peek());
|
||||
getChildren().setAll(stack.get(stack.size() - 1));
|
||||
|
||||
LOG.info(this + " " + stack + ", removed: " + flag + ", object: " + node);
|
||||
}
|
||||
@@ -39,6 +39,7 @@ import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import java.util.List;
|
||||
@@ -46,6 +47,7 @@ import java.util.List;
|
||||
@DefaultProperty("container")
|
||||
public class RipplerContainer extends StackPane {
|
||||
private static final String DEFAULT_STYLE_CLASS = "rippler-container";
|
||||
private static final Duration DURATION = Duration.millis(200);
|
||||
|
||||
private final ObjectProperty<Node> container = new SimpleObjectProperty<>(this, "container", null);
|
||||
private final StyleableObjectProperty<Paint> ripplerFill = new SimpleStyleableObjectProperty<>(StyleableProperties.RIPPLER_FILL,this, "ripplerFill", null);
|
||||
@@ -103,11 +105,10 @@ public class RipplerContainer extends StackPane {
|
||||
rectangle.heightProperty().bind(heightProperty());
|
||||
}));
|
||||
|
||||
setOnMouseEntered(e -> {
|
||||
new Transition() {
|
||||
if (AnimationUtils.isAnimationEnabled()) {
|
||||
setOnMouseEntered(e -> new Transition() {
|
||||
{
|
||||
setCycleCount(1);
|
||||
setCycleDuration(Duration.millis(200));
|
||||
setCycleDuration(DURATION);
|
||||
setInterpolator(Interpolator.EASE_IN);
|
||||
}
|
||||
|
||||
@@ -115,14 +116,11 @@ public class RipplerContainer extends StackPane {
|
||||
protected void interpolate(double frac) {
|
||||
interpolateBackground(frac);
|
||||
}
|
||||
}.play();
|
||||
});
|
||||
}.play());
|
||||
|
||||
setOnMouseExited(e -> {
|
||||
new Transition() {
|
||||
setOnMouseExited(e -> new Transition() {
|
||||
{
|
||||
setCycleCount(1);
|
||||
setCycleDuration(Duration.millis(200));
|
||||
setCycleDuration(DURATION);
|
||||
setInterpolator(Interpolator.EASE_OUT);
|
||||
}
|
||||
|
||||
@@ -130,8 +128,11 @@ public class RipplerContainer extends StackPane {
|
||||
protected void interpolate(double frac) {
|
||||
interpolateBackground(1 - frac);
|
||||
}
|
||||
}.play();
|
||||
});
|
||||
}.play());
|
||||
} else {
|
||||
setOnMouseEntered(e -> interpolateBackground(1));
|
||||
setOnMouseExited(e -> interpolateBackground(0));
|
||||
}
|
||||
}
|
||||
|
||||
private void interpolateBackground(double frac) {
|
||||
|
||||
@@ -157,23 +157,5 @@ public class SpinnerPane extends Control {
|
||||
}
|
||||
}
|
||||
|
||||
public interface State {}
|
||||
|
||||
public static class LoadedState implements State {}
|
||||
|
||||
public static class LoadingState implements State {}
|
||||
|
||||
public static class FailedState implements State {
|
||||
private final String reason;
|
||||
|
||||
public FailedState(String reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public String getReason() {
|
||||
return reason;
|
||||
}
|
||||
}
|
||||
|
||||
public static final EventType<Event> FAILED_ACTION = new EventType<>(Event.ANY, "FAILED_ACTION");
|
||||
}
|
||||
@@ -18,6 +18,9 @@
|
||||
package org.jackhuang.hmcl.ui.decorator;
|
||||
|
||||
import com.jfoenix.controls.JFXSnackbar;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
@@ -35,6 +38,9 @@ import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
import org.jackhuang.hmcl.ui.wizard.Navigation;
|
||||
|
||||
public class Decorator extends Control {
|
||||
@@ -61,12 +67,40 @@ public class Decorator extends Control {
|
||||
private final ReadOnlyBooleanWrapper allowMove = new ReadOnlyBooleanWrapper();
|
||||
private final ReadOnlyBooleanWrapper dragging = new ReadOnlyBooleanWrapper();
|
||||
|
||||
private boolean playRestoreMinimizeAnimation = false;
|
||||
|
||||
public Decorator(Stage primaryStage) {
|
||||
this.primaryStage = primaryStage;
|
||||
|
||||
setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
|
||||
primaryStage.initStyle(StageStyle.UNDECORATED);
|
||||
|
||||
if (AnimationUtils.playWindowAnimation()) {
|
||||
FXUtils.onChange(primaryStage.iconifiedProperty(), iconified -> {
|
||||
if (playRestoreMinimizeAnimation && !iconified) {
|
||||
playRestoreMinimizeAnimation = false;
|
||||
Timeline timeline = new Timeline(
|
||||
new KeyFrame(Duration.millis(0),
|
||||
new KeyValue(this.opacityProperty(), 0, FXUtils.EASE),
|
||||
new KeyValue(this.translateYProperty(), 200, FXUtils.EASE),
|
||||
new KeyValue(this.scaleXProperty(), 0.4, FXUtils.EASE),
|
||||
new KeyValue(this.scaleYProperty(), 0.4, FXUtils.EASE),
|
||||
new KeyValue(this.scaleZProperty(), 0.4, FXUtils.EASE)
|
||||
),
|
||||
new KeyFrame(Duration.millis(200),
|
||||
new KeyValue(this.opacityProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(this.translateYProperty(), 0, FXUtils.EASE),
|
||||
new KeyValue(this.scaleXProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(this.scaleYProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(this.scaleZProperty(), 1, FXUtils.EASE)
|
||||
)
|
||||
);
|
||||
timeline.play();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Stage getPrimaryStage() {
|
||||
@@ -239,7 +273,29 @@ public class Decorator extends Control {
|
||||
}
|
||||
|
||||
public void minimize() {
|
||||
primaryStage.setIconified(true);
|
||||
if (AnimationUtils.playWindowAnimation()) {
|
||||
playRestoreMinimizeAnimation = true;
|
||||
Timeline timeline = new Timeline(
|
||||
new KeyFrame(Duration.millis(0),
|
||||
new KeyValue(this.opacityProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(this.translateYProperty(), 0, FXUtils.EASE),
|
||||
new KeyValue(this.scaleXProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(this.scaleYProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(this.scaleZProperty(), 1, FXUtils.EASE)
|
||||
),
|
||||
new KeyFrame(Duration.millis(200),
|
||||
new KeyValue(this.opacityProperty(), 0, FXUtils.EASE),
|
||||
new KeyValue(this.translateYProperty(), 200, FXUtils.EASE),
|
||||
new KeyValue(this.scaleXProperty(), 0.4, FXUtils.EASE),
|
||||
new KeyValue(this.scaleYProperty(), 0.4, FXUtils.EASE),
|
||||
new KeyValue(this.scaleZProperty(), 0.4, FXUtils.EASE)
|
||||
)
|
||||
);
|
||||
timeline.setOnFinished(event -> primaryStage.setIconified(true));
|
||||
timeline.play();
|
||||
} else {
|
||||
primaryStage.setIconified(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
|
||||
@@ -31,7 +31,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class DecoratorAnimationProducer implements AnimationProducer {
|
||||
public final class DecoratorAnimationProducer implements AnimationProducer {
|
||||
@Override
|
||||
public void init(AnimationHandler handler) {
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@ package org.jackhuang.hmcl.ui.decorator;
|
||||
|
||||
import com.jfoenix.controls.JFXDialog;
|
||||
import com.jfoenix.controls.JFXSnackbar;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.WeakInvalidationListener;
|
||||
@@ -35,6 +38,7 @@ import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDnD;
|
||||
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
|
||||
@@ -42,11 +46,12 @@ import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.account.AddAuthlibInjectorServerPane;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogAware;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.Navigator;
|
||||
import org.jackhuang.hmcl.ui.construct.StackContainerPane;
|
||||
import org.jackhuang.hmcl.ui.construct.JFXDialogPane;
|
||||
import org.jackhuang.hmcl.ui.wizard.Refreshable;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -77,11 +82,32 @@ public class DecoratorController {
|
||||
private final Navigator navigator;
|
||||
|
||||
private JFXDialog dialog;
|
||||
private StackContainerPane dialogPane;
|
||||
private JFXDialogPane dialogPane;
|
||||
|
||||
public DecoratorController(Stage stage, Node mainPage) {
|
||||
decorator = new Decorator(stage);
|
||||
decorator.setOnCloseButtonAction(Launcher::stopApplication);
|
||||
decorator.setOnCloseButtonAction(() -> {
|
||||
if (AnimationUtils.playWindowAnimation()) {
|
||||
Timeline timeline = new Timeline(
|
||||
new KeyFrame(Duration.millis(0),
|
||||
new KeyValue(decorator.opacityProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(decorator.scaleXProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(decorator.scaleYProperty(), 1, FXUtils.EASE),
|
||||
new KeyValue(decorator.scaleZProperty(), 0.3, FXUtils.EASE)
|
||||
),
|
||||
new KeyFrame(Duration.millis(200),
|
||||
new KeyValue(decorator.opacityProperty(), 0, FXUtils.EASE),
|
||||
new KeyValue(decorator.scaleXProperty(), 0.8, FXUtils.EASE),
|
||||
new KeyValue(decorator.scaleYProperty(), 0.8, FXUtils.EASE),
|
||||
new KeyValue(decorator.scaleZProperty(), 0.8, FXUtils.EASE)
|
||||
)
|
||||
);
|
||||
timeline.setOnFinished(event -> Launcher.stopApplication());
|
||||
timeline.play();
|
||||
} else {
|
||||
Launcher.stopApplication();
|
||||
}
|
||||
});
|
||||
decorator.titleTransparentProperty().bind(config().titleTransparentProperty());
|
||||
|
||||
navigator = new Navigator();
|
||||
@@ -354,7 +380,7 @@ public class DecoratorController {
|
||||
return;
|
||||
}
|
||||
dialog = new JFXDialog();
|
||||
dialogPane = new StackContainerPane();
|
||||
dialogPane = new JFXDialogPane();
|
||||
|
||||
dialog.setContent(dialogPane);
|
||||
decorator.capableDraggingWindow(dialog);
|
||||
@@ -397,18 +423,21 @@ public class DecoratorController {
|
||||
.ifPresent(handler -> node.removeEventHandler(DialogCloseEvent.CLOSE, (EventHandler<DialogCloseEvent>) handler));
|
||||
|
||||
if (dialog != null) {
|
||||
dialogPane.pop(node);
|
||||
JFXDialogPane pane = dialogPane;
|
||||
|
||||
if (node instanceof DialogAware) {
|
||||
((DialogAware) node).onDialogClosed();
|
||||
}
|
||||
|
||||
if (dialogPane.getChildren().isEmpty()) {
|
||||
if (pane.size() == 1 && pane.peek().orElse(null) == node) {
|
||||
dialog.setOnDialogClosed(e -> pane.pop(node));
|
||||
dialog.close();
|
||||
dialog = null;
|
||||
dialogPane = null;
|
||||
|
||||
navigator.setDisable(false);
|
||||
} else {
|
||||
pane.pop(node);
|
||||
}
|
||||
|
||||
if (node instanceof DialogAware) {
|
||||
((DialogAware) node).onDialogClosed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,16 +187,19 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
buttonsContainer.setMaxHeight(40);
|
||||
{
|
||||
JFXButton btnHelp = new JFXButton();
|
||||
btnHelp.setFocusTraversable(false);
|
||||
btnHelp.setGraphic(SVG.HELP_CIRCLE_OUTLINE.createIcon(Theme.foregroundFillBinding(), -1, -1));
|
||||
btnHelp.getStyleClass().add("jfx-decorator-button");
|
||||
btnHelp.setOnAction(e -> FXUtils.openLink("https://docs.hmcl.net/help.html"));
|
||||
|
||||
JFXButton btnMin = new JFXButton();
|
||||
btnMin.setFocusTraversable(false);
|
||||
btnMin.setGraphic(SVG.MINUS.createIcon(Theme.foregroundFillBinding(), -1, -1));
|
||||
btnMin.getStyleClass().add("jfx-decorator-button");
|
||||
btnMin.setOnAction(e -> skinnable.minimize());
|
||||
|
||||
JFXButton btnClose = new JFXButton();
|
||||
btnClose.setFocusTraversable(false);
|
||||
btnClose.setGraphic(SVG.CLOSE.createIcon(Theme.foregroundFillBinding(), -1, -1));
|
||||
btnClose.getStyleClass().add("jfx-decorator-button");
|
||||
btnClose.setOnAction(e -> skinnable.close());
|
||||
@@ -224,6 +227,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
|
||||
if (canBack) {
|
||||
JFXButton backNavButton = new JFXButton();
|
||||
backNavButton.setFocusTraversable(false);
|
||||
backNavButton.setGraphic(SVG.BACK.createIcon(Theme.foregroundFillBinding(), -1, -1));
|
||||
backNavButton.getStyleClass().add("jfx-decorator-button");
|
||||
backNavButton.ripplerFillProperty().set(Theme.whiteFill());
|
||||
@@ -235,6 +239,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
|
||||
if (canClose) {
|
||||
JFXButton closeNavButton = new JFXButton();
|
||||
closeNavButton.setFocusTraversable(false);
|
||||
closeNavButton.setGraphic(SVG.CLOSE.createIcon(Theme.foregroundFillBinding(), -1, -1));
|
||||
closeNavButton.getStyleClass().add("jfx-decorator-button");
|
||||
closeNavButton.ripplerFillProperty().set(Theme.whiteFill());
|
||||
@@ -258,7 +263,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
titleLabel.getStyleClass().add("jfx-decorator-title");
|
||||
if (titleNode == null) {
|
||||
titleLabel.maxWidthProperty().bind(Bindings.createDoubleBinding(
|
||||
() -> skinnable.getWidth() - 100 - navLeft.getWidth(),
|
||||
() -> skinnable.getWidth() - 150 - navLeft.getWidth(),
|
||||
skinnable.widthProperty(), navLeft.widthProperty()));
|
||||
} else {
|
||||
titleLabel.prefWidthProperty().bind(Bindings.createDoubleBinding(() -> {
|
||||
|
||||
@@ -181,6 +181,7 @@ public final class JavaInstallPage extends WizardSinglePage {
|
||||
pane.setLeft(new Label(name));
|
||||
|
||||
Label valueLabel = FXUtils.truncatedLabel(value, 60);
|
||||
FXUtils.copyOnDoubleClick(valueLabel);
|
||||
BorderPane.setAlignment(valueLabel, Pos.CENTER_RIGHT);
|
||||
pane.setCenter(valueLabel);
|
||||
|
||||
|
||||
@@ -62,9 +62,9 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
|
||||
tab = new TabHeader(gameTab, javaManagementTab, settingsTab, personalizationTab, downloadTab, helpTab, feedbackTab, aboutTab);
|
||||
|
||||
tab.select(gameTab);
|
||||
gameTab.initializeIfNeeded();
|
||||
gameTab.getNode().loadVersion(Profiles.getSelectedProfile(), null);
|
||||
FXUtils.onChangeAndOperate(tab.getSelectionModel().selectedItemProperty(), newValue -> {
|
||||
transitionPane.setContent(gameTab.getNode(), ContainerAnimations.NONE);
|
||||
FXUtils.onChange(tab.getSelectionModel().selectedItemProperty(), newValue -> {
|
||||
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE);
|
||||
});
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.nbt.NBTEditorPage;
|
||||
import org.jackhuang.hmcl.ui.nbt.NBTHelper;
|
||||
import org.jackhuang.hmcl.ui.nbt.NBTFileType;
|
||||
import org.jackhuang.hmcl.ui.versions.GameAdvancedListItem;
|
||||
import org.jackhuang.hmcl.ui.versions.Versions;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
@@ -90,16 +90,16 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
if (mainPage == null) {
|
||||
MainPage mainPage = new MainPage();
|
||||
FXUtils.applyDragListener(mainPage,
|
||||
file -> ModpackHelper.isFileModpackByExtension(file) || NBTHelper.isNBTFileByExtension(file),
|
||||
file -> ModpackHelper.isFileModpackByExtension(file) || NBTFileType.isNBTFileByExtension(file.toPath()),
|
||||
modpacks -> {
|
||||
File file = modpacks.get(0);
|
||||
if (ModpackHelper.isFileModpackByExtension(file)) {
|
||||
Controllers.getDecorator().startWizard(
|
||||
new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), file),
|
||||
i18n("install.modpack"));
|
||||
} else if (NBTHelper.isNBTFileByExtension(file)) {
|
||||
} else if (NBTFileType.isNBTFileByExtension(file.toPath())) {
|
||||
try {
|
||||
Controllers.navigate(new NBTEditorPage(file));
|
||||
Controllers.navigate(new NBTEditorPage(file.toPath()));
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Fail to open nbt file", e);
|
||||
Controllers.dialog(i18n("nbt.open.failed") + "\n\n" + StringUtils.getStackTrace(e),
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.nbt;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
@@ -5,33 +22,38 @@ import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.ProgressIndicator;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.PageCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class NBTEditorPage extends BorderPane implements DecoratorPage {
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class NBTEditorPage extends SpinnerPane implements DecoratorPage {
|
||||
private final ReadOnlyObjectWrapper<State> state;
|
||||
private final File file;
|
||||
private final Path file;
|
||||
private final NBTFileType type;
|
||||
|
||||
public NBTEditorPage(File file) throws IOException {
|
||||
private final BorderPane root = new BorderPane();
|
||||
|
||||
public NBTEditorPage(Path file) throws IOException {
|
||||
getStyleClass().add("gray-background");
|
||||
|
||||
this.state = new ReadOnlyObjectWrapper<>(DecoratorPage.State.fromTitle(i18n("nbt.title", file.getAbsolutePath())));
|
||||
this.state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("nbt.title", file.toString())));
|
||||
this.file = file;
|
||||
this.type = NBTFileType.ofFile(file);
|
||||
|
||||
@@ -39,7 +61,8 @@ public class NBTEditorPage extends BorderPane implements DecoratorPage {
|
||||
throw new IOException("Unknown type of file " + file);
|
||||
}
|
||||
|
||||
setCenter(new ProgressIndicator());
|
||||
setContent(root);
|
||||
setLoading(true);
|
||||
|
||||
HBox actions = new HBox(8);
|
||||
actions.setPadding(new Insets(8));
|
||||
@@ -65,18 +88,16 @@ public class NBTEditorPage extends BorderPane implements DecoratorPage {
|
||||
|
||||
actions.getChildren().setAll(saveButton, cancelButton);
|
||||
|
||||
CompletableFuture.supplyAsync(Lang.wrap(() -> type.readAsTree(file)))
|
||||
.thenAcceptAsync(tree -> {
|
||||
setCenter(new NBTTreeView(tree));
|
||||
// setBottom(actions);
|
||||
}, Schedulers.javafx())
|
||||
.handleAsync((result, e) -> {
|
||||
if (e != null) {
|
||||
LOG.warning("Fail to open nbt file", e);
|
||||
Controllers.dialog(i18n("nbt.open.failed") + "\n\n" + StringUtils.getStackTrace(e), null, MessageDialogPane.MessageType.WARNING, cancelButton::fire);
|
||||
Task.supplyAsync(() -> type.readAsTree(file))
|
||||
.whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
if (exception == null) {
|
||||
setLoading(false);
|
||||
root.setCenter(new NBTTreeView(result));
|
||||
} else {
|
||||
LOG.warning("Fail to open nbt file", exception);
|
||||
Controllers.dialog(i18n("nbt.open.failed") + "\n\n" + StringUtils.getStackTrace(exception), null, MessageDialogPane.MessageType.WARNING, cancelButton::fire);
|
||||
}
|
||||
return null;
|
||||
}, Schedulers.javafx());
|
||||
}).start();
|
||||
}
|
||||
|
||||
public void save() throws IOException {
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.nbt;
|
||||
|
||||
import com.github.steveice10.opennbt.NBTIO;
|
||||
@@ -9,16 +26,21 @@ import org.apache.commons.compress.utils.BoundedInputStream;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.Inflater;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public enum NBTFileType {
|
||||
COMPRESSED("dat", "dat_old") {
|
||||
@Override
|
||||
public Tag read(File file) throws IOException {
|
||||
try (BufferedInputStream fileInputStream = new BufferedInputStream(new FileInputStream(file))) {
|
||||
public Tag read(Path file) throws IOException {
|
||||
try (BufferedInputStream fileInputStream = new BufferedInputStream(Files.newInputStream(file))) {
|
||||
fileInputStream.mark(3);
|
||||
byte[] header = new byte[3];
|
||||
if (fileInputStream.read(header) < 3) {
|
||||
@@ -42,25 +64,28 @@ public enum NBTFileType {
|
||||
},
|
||||
ANVIL("mca") {
|
||||
@Override
|
||||
public Tag read(File file) throws IOException {
|
||||
public Tag read(Path file) throws IOException {
|
||||
return REGION.read(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NBTTreeView.Item readAsTree(File file) throws IOException {
|
||||
public NBTTreeView.Item readAsTree(Path file) throws IOException {
|
||||
return REGION.readAsTree(file);
|
||||
}
|
||||
},
|
||||
REGION("mcr") {
|
||||
@Override
|
||||
public Tag read(File file) throws IOException {
|
||||
try (RandomAccessFile r = new RandomAccessFile(file, "r")) {
|
||||
public Tag read(Path file) throws IOException {
|
||||
try (RandomAccessFile r = new RandomAccessFile(file.toFile(), "r")) {
|
||||
ListTag tag = new ListTag(file.getFileName().toString(), CompoundTag.class);
|
||||
if (r.length() == 0) {
|
||||
return tag;
|
||||
}
|
||||
|
||||
byte[] header = new byte[4096];
|
||||
byte[] buffer = new byte[1 * 1024 * 1024]; // The maximum size of each chunk is 1MiB
|
||||
Inflater inflater = new Inflater();
|
||||
|
||||
ListTag tag = new ListTag(file.getName(), CompoundTag.class);
|
||||
|
||||
r.readFully(header);
|
||||
for (int i = 0; i < 4096; i += 4) {
|
||||
int offset = ((header[i] & 0xff) << 16) + ((header[i + 1] & 0xff) << 8) + (header[i + 2] & 0xff);
|
||||
@@ -109,7 +134,7 @@ public enum NBTFileType {
|
||||
}
|
||||
|
||||
@Override
|
||||
public NBTTreeView.Item readAsTree(File file) throws IOException {
|
||||
public NBTTreeView.Item readAsTree(Path file) throws IOException {
|
||||
NBTTreeView.Item item = new NBTTreeView.Item(read(file));
|
||||
|
||||
for (Tag tag : ((ListTag) item.getValue())) {
|
||||
@@ -135,7 +160,11 @@ public enum NBTFileType {
|
||||
|
||||
static final NBTFileType[] types = values();
|
||||
|
||||
public static NBTFileType ofFile(File file) {
|
||||
public static boolean isNBTFileByExtension(Path file) {
|
||||
return NBTFileType.ofFile(file) != null;
|
||||
}
|
||||
|
||||
public static NBTFileType ofFile(Path file) {
|
||||
String ext = FileUtils.getExtension(file);
|
||||
for (NBTFileType type : types) {
|
||||
for (String extension : type.extensions) {
|
||||
@@ -153,11 +182,11 @@ public enum NBTFileType {
|
||||
this.extensions = extensions;
|
||||
}
|
||||
|
||||
public abstract Tag read(File file) throws IOException;
|
||||
public abstract Tag read(Path file) throws IOException;
|
||||
|
||||
public NBTTreeView.Item readAsTree(File file) throws IOException {
|
||||
public NBTTreeView.Item readAsTree(Path file) throws IOException {
|
||||
NBTTreeView.Item root = NBTTreeView.buildTree(read(file));
|
||||
root.setName(file.getName());
|
||||
root.setName(file.getFileName().toString());
|
||||
return root;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package org.jackhuang.hmcl.ui.nbt;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public final class NBTHelper {
|
||||
private NBTHelper() {
|
||||
}
|
||||
|
||||
public static boolean isNBTFileByExtension(File file) {
|
||||
return NBTFileType.ofFile(file) != null;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.nbt;
|
||||
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
@@ -6,6 +23,9 @@ import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public enum NBTTagType {
|
||||
BYTE, SHORT, INT, LONG, FLOAT, DOUBLE,
|
||||
BYTE_ARRAY, INT_ARRAY, LONG_ARRAY,
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.nbt;
|
||||
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
@@ -14,7 +31,10 @@ import java.util.EnumMap;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class NBTTreeView extends TreeView<Tag> {
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class NBTTreeView extends TreeView<Tag> {
|
||||
|
||||
public NBTTreeView(NBTTreeView.Item tree) {
|
||||
this.setRoot(tree);
|
||||
|
||||
@@ -398,6 +398,7 @@ class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
setHeading(titleContainer);
|
||||
|
||||
Label description = new Label(modInfo.getModInfo().getDescription().toString());
|
||||
FXUtils.copyOnDoubleClick(description);
|
||||
setBody(description);
|
||||
|
||||
if (StringUtils.isNotBlank(modInfo.getModInfo().getId())) {
|
||||
|
||||
@@ -66,7 +66,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
|
||||
private String preferredVersionName = null;
|
||||
|
||||
{
|
||||
public VersionPage() {
|
||||
versionSettingsTab.setNodeSupplier(loadVersionFor(() -> new VersionSettingsPage(false)));
|
||||
installerListTab.setNodeSupplier(loadVersionFor(InstallerListPage::new));
|
||||
modListTab.setNodeSupplier(loadVersionFor(ModListPage::new));
|
||||
@@ -77,7 +77,8 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated);
|
||||
|
||||
tab.select(versionSettingsTab);
|
||||
FXUtils.onChangeAndOperate(tab.getSelectionModel().selectedItemProperty(), newValue -> {
|
||||
transitionPane.setContent(versionSettingsTab.getNode(), ContainerAnimations.NONE);
|
||||
FXUtils.onChange(tab.getSelectionModel().selectedItemProperty(), newValue -> {
|
||||
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE);
|
||||
});
|
||||
|
||||
|
||||
@@ -551,8 +551,8 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
javaCustomOption.valueProperty().unbindBidirectional(lastVersionSetting.javaDirProperty());
|
||||
gameDirCustomOption.valueProperty().unbindBidirectional(lastVersionSetting.gameDirProperty());
|
||||
FXUtils.unbind(txtServerIP, lastVersionSetting.serverIpProperty());
|
||||
FXUtils.unbindBoolean(chkAutoAllocate, lastVersionSetting.autoMemoryProperty());
|
||||
FXUtils.unbindBoolean(chkFullscreen, lastVersionSetting.fullscreenProperty());
|
||||
chkAutoAllocate.selectedProperty().unbindBidirectional(lastVersionSetting.autoMemoryProperty());
|
||||
chkFullscreen.selectedProperty().unbindBidirectional(lastVersionSetting.fullscreenProperty());
|
||||
showLogsPane.selectedProperty().unbindBidirectional(lastVersionSetting.showLogsProperty());
|
||||
FXUtils.unbindEnum(cboLauncherVisibility, lastVersionSetting.launcherVisibilityProperty());
|
||||
FXUtils.unbindEnum(cboProcessPriority, lastVersionSetting.processPriorityProperty());
|
||||
@@ -586,8 +586,8 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
javaCustomOption.bindBidirectional(versionSetting.javaDirProperty());
|
||||
gameDirCustomOption.bindBidirectional(versionSetting.gameDirProperty());
|
||||
FXUtils.bindString(txtServerIP, versionSetting.serverIpProperty());
|
||||
FXUtils.bindBoolean(chkAutoAllocate, versionSetting.autoMemoryProperty());
|
||||
FXUtils.bindBoolean(chkFullscreen, versionSetting.fullscreenProperty());
|
||||
chkAutoAllocate.selectedProperty().bindBidirectional(versionSetting.autoMemoryProperty());
|
||||
chkFullscreen.selectedProperty().bindBidirectional(versionSetting.fullscreenProperty());
|
||||
showLogsPane.selectedProperty().bindBidirectional(versionSetting.showLogsProperty());
|
||||
FXUtils.bindEnum(cboLauncherVisibility, versionSetting.launcherVisibilityProperty());
|
||||
FXUtils.bindEnum(cboProcessPriority, versionSetting.processPriorityProperty());
|
||||
|
||||
@@ -103,6 +103,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
|
||||
|
||||
Label worldNameLabel = new Label();
|
||||
FXUtils.copyOnDoubleClick(worldNameLabel);
|
||||
worldNameLabel.setText(world.getWorldName());
|
||||
BorderPane.setAlignment(worldNameLabel, Pos.CENTER_RIGHT);
|
||||
worldNamePane.setRight(worldNameLabel);
|
||||
@@ -115,6 +116,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
gameVersionPane.setLeft(label);
|
||||
|
||||
Label gameVersionLabel = new Label();
|
||||
FXUtils.copyOnDoubleClick(gameVersionLabel);
|
||||
gameVersionLabel.setText(world.getGameVersion());
|
||||
BorderPane.setAlignment(gameVersionLabel, Pos.CENTER_RIGHT);
|
||||
gameVersionPane.setRight(gameVersionLabel);
|
||||
@@ -127,6 +129,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
randomSeedPane.setLeft(label);
|
||||
|
||||
Label randomSeedLabel = new Label();
|
||||
FXUtils.copyOnDoubleClick(randomSeedLabel);
|
||||
BorderPane.setAlignment(randomSeedLabel, Pos.CENTER_RIGHT);
|
||||
randomSeedPane.setRight(randomSeedLabel);
|
||||
|
||||
@@ -143,6 +146,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
lastPlayedPane.setLeft(label);
|
||||
|
||||
Label lastPlayedLabel = new Label();
|
||||
FXUtils.copyOnDoubleClick(lastPlayedLabel);
|
||||
lastPlayedLabel.setText(formatDateTime(Instant.ofEpochMilli(world.getLastPlayed())));
|
||||
BorderPane.setAlignment(lastPlayedLabel, Pos.CENTER_RIGHT);
|
||||
lastPlayedPane.setRight(lastPlayedLabel);
|
||||
@@ -155,6 +159,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
timePane.setLeft(label);
|
||||
|
||||
Label timeLabel = new Label();
|
||||
FXUtils.copyOnDoubleClick(timeLabel);
|
||||
BorderPane.setAlignment(timeLabel, Pos.CENTER_RIGHT);
|
||||
timePane.setRight(timeLabel);
|
||||
|
||||
@@ -258,6 +263,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
locationPane.setLeft(label);
|
||||
|
||||
Label locationLabel = new Label();
|
||||
FXUtils.copyOnDoubleClick(locationLabel);
|
||||
BorderPane.setAlignment(locationLabel, Pos.CENTER_RIGHT);
|
||||
locationPane.setRight(locationLabel);
|
||||
|
||||
@@ -276,6 +282,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
lastDeathLocationPane.setLeft(label);
|
||||
|
||||
Label lastDeathLocationLabel = new Label();
|
||||
FXUtils.copyOnDoubleClick(lastDeathLocationLabel);
|
||||
BorderPane.setAlignment(lastDeathLocationLabel, Pos.CENTER_RIGHT);
|
||||
lastDeathLocationPane.setRight(lastDeathLocationLabel);
|
||||
|
||||
@@ -297,6 +304,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
|
||||
spawnPane.setLeft(label);
|
||||
|
||||
Label spawnLabel = new Label();
|
||||
FXUtils.copyOnDoubleClick(spawnLabel);
|
||||
BorderPane.setAlignment(spawnLabel, Pos.CENTER_RIGHT);
|
||||
spawnPane.setRight(spawnLabel);
|
||||
|
||||
|
||||
@@ -34,8 +34,8 @@ public final class Lazy<T> {
|
||||
}
|
||||
|
||||
public T get() {
|
||||
if (value == null) {
|
||||
value = Objects.requireNonNull(supplier.get());
|
||||
if (supplier != null) {
|
||||
value = supplier.get();
|
||||
supplier = null;
|
||||
}
|
||||
return value;
|
||||
|
||||
@@ -122,7 +122,7 @@ account.methods.microsoft.manual=Ваш код устройства - <b>%1$s</b
|
||||
account.methods.microsoft.makegameidsettings=Создать / редактировать профиль
|
||||
account.methods.microsoft.profile=Профиль аккаунта...
|
||||
account.methods.microsoft.purchase=Купить Minecraft
|
||||
account.methods.microsoft.snapshot=Вы используете неофициальное построение HMCL, загрузите официальное построение для входа в Microsoft.
|
||||
account.methods.microsoft.snapshot=Вы используете неофициальную версию HMCL, загрузите официальную версию для входа в аккаунт Microsoft.
|
||||
account.methods.microsoft.snapshot.website=Официальный сайт
|
||||
account.methods.offline=Офлайн
|
||||
account.methods.offline.name.special_characters=Рекомендуется использовать только английские буквы, цифры и символы подчёркивания.
|
||||
@@ -500,7 +500,7 @@ install.installer.quilt=Quilt
|
||||
install.installer.quilt-api=QSL/QFAPI
|
||||
install.installer.version=%s
|
||||
install.modpack=Установить модпак
|
||||
install.new_game=Добавить новый экземпляр
|
||||
install.new_game=Установить сборку
|
||||
install.new_game.already_exists=Экземпляр уже существует. Используйте другое имя.
|
||||
install.new_game.current_game_version=Текущая версия экземпляра
|
||||
install.new_game.malformed=Недопустимое имя
|
||||
@@ -626,17 +626,20 @@ message.success=Операция успешно завершена
|
||||
message.unknown=Неизвестно
|
||||
message.warning=Предупреждение
|
||||
|
||||
modpack=Модпак
|
||||
modpack=Модпаки
|
||||
modpack.choose=Выберите модпак
|
||||
modpack.choose.local=Импорт из файла
|
||||
modpack.choose.local.detail=Можно перетащить файл модпака сюда
|
||||
modpack.choose.remote=Скачать
|
||||
modpack.choose.remote.detail=Необходима прямая ссылка на скачивание модпака
|
||||
modpack.choose.remote.tooltip=Введите URL-адрес модпака
|
||||
modpack.choose.repository=Скачать модпак с CurseForge или Modrinth
|
||||
modpack.choose.repository.detail=Не забудьте вернуться на эту страницу и перетащить файл модпака сюда после того, как модпак будет скачан.
|
||||
modpack.completion=Скачивание зависимостей
|
||||
modpack.desc=Опишите модпак, включая введение и журнал изменений. Поддерживаются Markdown и изображения по URL-адресу.
|
||||
modpack.description=Описание
|
||||
modpack.download=Скачать модпак
|
||||
modpack.download.title=Скачать модпак - %1s
|
||||
modpack.enter_name=Введите имя модпака.
|
||||
modpack.export=Экспортировать модпак
|
||||
modpack.export.as=Экспортировать модпак как...
|
||||
@@ -645,20 +648,20 @@ modpack.files.blueprints=Чертежи BuildCraft
|
||||
modpack.files.config=Конфигурации модов
|
||||
modpack.files.dumps=Выходной файл отладки NEI
|
||||
modpack.files.hmclversion_cfg=Файл конфигурации лаунчера
|
||||
modpack.files.liteconfig=Файл конфигурации мода
|
||||
modpack.files.liteconfig=Связанные файлы LiteLoader
|
||||
modpack.files.mods=Моды
|
||||
modpack.files.mods.voxelmods=Параметры VoxelMods
|
||||
modpack.files.options_txt=Файл настроек Minecraft
|
||||
modpack.files.optionsshaders_txt=Файл настроек шейдера
|
||||
modpack.files.resourcepacks=Текстурпаки
|
||||
modpack.files.saves=Сохранения игры
|
||||
modpack.files.resourcepacks=Пакеты ресурсов/текстур
|
||||
modpack.files.saves=миры
|
||||
modpack.files.scripts=Файл конфигурации MineTweaker
|
||||
modpack.files.servers_dat=Файл списка серверов
|
||||
modpack.install=Установить модпак %s
|
||||
modpack.installing=Установка модпака
|
||||
modpack.introduction=Поддерживаются модпаки CurseForge, Modrinth, MultiMC и MCBBS.
|
||||
modpack.invalid=Неверный модпак, попробуйте скачать его заново.
|
||||
modpack.mismatched_type=Несоответствие типа модпака, текущий экземпляр имеет тип %s, но предоставленный экземпляр имеет тип %s.
|
||||
modpack.mismatched_type=Несоответствие типа модпака, текущий сборка имеет тип %s, но предоставленный сборка имеет тип %s.
|
||||
modpack.name=Имя модпака
|
||||
modpack.not_a_valid_name=Неверное имя модпака
|
||||
modpack.origin=Источник
|
||||
@@ -672,95 +675,168 @@ modpack.type.curse=Curse
|
||||
modpack.type.curse.error=Невозможно установить этот модпак. Повторите ещё раз.
|
||||
modpack.type.curse.not_found=Некоторые из необходимых ресурсов отсутствуют и поэтому не могут быть загружены. Скачайте последнюю версию или другие модпаки.
|
||||
modpack.type.manual.warning=Возможно, вам нужно напрямую распаковать файл этого модпака, а не импортировать его. И запустите игру с помощью прилагаемого лаунчера. Этот модпак создаётся вручную путем сжатия директории .minecraft, а не экспортируется лаунчером. HMCL может попытаться импортировать этот модпак, продолжать?
|
||||
modpack.type.mcbbs=Стандарт MCBBS
|
||||
modpack.type.mcbbs=MCBBS
|
||||
modpack.type.mcbbs.export=Может быть импортирован Hello Minecraft! Launcher
|
||||
modpack.type.modrinth=Modrinth
|
||||
modpack.type.multimc=MultiMC
|
||||
modpack.type.multimc.export=Может быть импортирован Hello Minecraft! Launcher и MultiMC
|
||||
modpack.type.server=Сервер автообновления модпака
|
||||
modpack.type.server.export=Разрешить менеджеру сервера удаленно обновлять клиент игры
|
||||
modpack.type.server.malformed=Некорректный манифест модпака сервера
|
||||
modpack.unsupported=Hello Minecraft! Launcher не поддерживает этот формат пакетов
|
||||
modpack.update=Обновить игру
|
||||
modpack.update=Обновление модпака
|
||||
modpack.wizard=Мастер экспорта модпака
|
||||
modpack.wizard.step.1=Основные параметры
|
||||
modpack.wizard.step.1.title=Установка основных параметров для модпака.
|
||||
modpack.wizard.step.1=Основные настройки
|
||||
modpack.wizard.step.1.title=Некоторые основные сведения о модпаке.
|
||||
modpack.wizard.step.2=Выбрать файлы
|
||||
modpack.wizard.step.2.title=Выберите файлы, которые вы хотите добавить в модпак.
|
||||
modpack.wizard.step.3=Тип модпака
|
||||
modpack.wizard.step.3.title=Выберите тип модпака, который вы хотите экспортировать.
|
||||
modpack.wizard.step.initialization.exported_version=Версия игры для экспорта
|
||||
modpack.wizard.step.initialization.force_update=Принудительное обновление модпака до последней версии (вам понадобится служба хостинга файлов)
|
||||
|
||||
modpack.wizard.step.initialization.include_launcher=Включать лаунчер
|
||||
modpack.wizard.step.initialization.save=Экспортировать в...
|
||||
modpack.wizard.step.initialization.warning=Перед созданием модпака, убедитесь что игра запускается, и это Release, а не Snapshot версия. Лаунчер сохранит настройки скачивания.\n\
|
||||
\n\
|
||||
Имейте в виду, что не разрешается добавлять моды и текстурпаки, в которых явно сказано, что они не должны распространяться или помещаться в модпаки.
|
||||
modpack.wizard.step.initialization.warning=Перед созданием модпака убедитесь, что игра запускается нормально и Minecraft является релизной версией, а не снапшотом. Пусковая установка сохранит ваши настройки скачивания.\n\
|
||||
\n\
|
||||
Помните, что вам не разрешается добавлять моды и пакет ресурсов, в которых явно указано, что они не подлежат распространению или помещению в модпак.
|
||||
modpack.wizard.step.initialization.server=Нажмите здесь для получения дополнительных руководств по созданию модпака на сервере, который может автообновляться.
|
||||
|
||||
modrinth.category.adventure=Adventure
|
||||
modrinth.category.adventure=Приключение
|
||||
modrinth.category.audio=Аудио
|
||||
modrinth.category.blocks=Блоки
|
||||
modrinth.category.bukkit=Bukkit
|
||||
modrinth.category.bungeecord=BungeeCord
|
||||
modrinth.category.challenging=Challenging
|
||||
modrinth.category.combat=Combat
|
||||
modrinth.category.challenging=Вызов
|
||||
modrinth.category.core-shaders=Ядро шейдеров
|
||||
modrinth.category.combat=Бой
|
||||
modrinth.category.cursed=Cursed
|
||||
modrinth.category.decoration=Decoration
|
||||
modrinth.category.economy=Economy
|
||||
modrinth.category.equipment=Equipment
|
||||
modrinth.category.decoration=Украшения
|
||||
modrinth.category.economy=Экономика
|
||||
modrinth.category.entities=Сущности
|
||||
modrinth.category.environment=Окружающая среда
|
||||
modrinth.category.equipment=Оборудование
|
||||
modrinth.category.fabric=Fabric
|
||||
modrinth.category.fonts=Шрифты
|
||||
modrinth.category.food=Еда
|
||||
modrinth.category.forge=Forge
|
||||
modrinth.category.game-mechanics=Игровая механика
|
||||
modrinth.category.gui=ГИП
|
||||
modrinth.category.items=Предметы
|
||||
modrinth.category.kitchen-sink=Kitchen-Sink
|
||||
modrinth.category.library=Библиотека
|
||||
modrinth.category.lightweight=Легкий
|
||||
modrinth.category.liteloader=LiteLoader
|
||||
modrinth.category.locale=Язык
|
||||
modrinth.category.magic=Магия
|
||||
modrinth.category.management=Управление
|
||||
modrinth.category.minecraft=Minecraft
|
||||
modrinth.category.minigame=Мини-игра
|
||||
modrinth.category.misc=Разное
|
||||
modrinth.category.mobs=Мобы
|
||||
modrinth.category.modded=Модифицированный
|
||||
modrinth.category.models=Модели
|
||||
modrinth.category.modloader=Мод-загрузчик
|
||||
modrinth.category.multiplayer=Сетевая игра
|
||||
modrinth.category.neoforge=NeoForge
|
||||
modrinth.category.optimization=Оптимизация
|
||||
modrinth.category.paper=Paper
|
||||
modrinth.category.purpur=Purpur
|
||||
modrinth.category.quests=Квесты
|
||||
modrinth.category.quilt=Quilt
|
||||
modrinth.category.realistic=Реалистичный
|
||||
modrinth.category.rift=Rift
|
||||
modrinth.category.simplistic=Упрощенный
|
||||
modrinth.category.social=Социальная
|
||||
modrinth.category.spigot=Spigot
|
||||
modrinth.category.sponge=Sponge
|
||||
modrinth.category.storage=Хранилище
|
||||
modrinth.category.technology=Технология
|
||||
modrinth.category.themed=Тематический
|
||||
modrinth.category.transportation=Транспорт
|
||||
modrinth.category.tweaks=Усовершенствование
|
||||
modrinth.category.utility=Утилиты
|
||||
modrinth.category.vanilla-like=Ванильное
|
||||
modrinth.category.velocity=Velocity
|
||||
modrinth.category.waterfall=Waterfall
|
||||
modrinth.category.worldgen=Генерация мира
|
||||
modrinth.category.datapack=Datapack
|
||||
modrinth.category.datapack=Набор данных
|
||||
modrinth.category.folia=Folia
|
||||
modrinth.category.8x-=8x-
|
||||
modrinth.category.16x=16x
|
||||
modrinth.category.32x=32x
|
||||
modrinth.category.48x=48x
|
||||
modrinth.category.64x=64x
|
||||
modrinth.category.128x=128x
|
||||
modrinth.category.256x=256x
|
||||
modrinth.category.512x+=512x+
|
||||
|
||||
mods=Моды
|
||||
mods.add=Добавить моды
|
||||
mods.add.failed=Не удаётся установить мод %s.
|
||||
mods.add.success=%s был успешно установлен.
|
||||
mods.add=Добавить мод
|
||||
mods.add.failed=Не удалось установить мод %s.
|
||||
mods.add.success=%s был успешно добавлен.
|
||||
mods.broken_dependency.title=Сломанная зависимость
|
||||
mods.broken_dependency.desc=Эта зависимость существовала раньше, но теперь ее нет. Попробуйте использовать другой источник скачивания.
|
||||
mods.category=Категория
|
||||
mods.channel.alpha=Альфа
|
||||
mods.channel.beta=Бета
|
||||
mods.channel.release=Релиз
|
||||
mods.check_updates=Проверить обновления
|
||||
mods.check_updates.current_version=Текущая версия
|
||||
mods.check_updates.empty=Все моды новейшие
|
||||
mods.check_updates.failed_check=Не удалось проверить обновления.
|
||||
mods.check_updates.failed_download=Не удаётся скачать некоторые файлы.
|
||||
mods.check_updates.failed_download=Не удалось скачать некоторые файлы.
|
||||
mods.check_updates.file=Файл
|
||||
mods.check_updates.source=Источник
|
||||
mods.check_updates.target_version=Целевая версия
|
||||
mods.check_updates.update=Обновить
|
||||
mods.choose_mod=Выберите мод
|
||||
mods.curseforge=CurseForge
|
||||
mods.dependency.embedded=Встроенные зависимости (Уже упакован в файл мода автором. Нет необходимости скачивать отдельно.)
|
||||
mods.dependency.optional=Необязательные зависимости (Если он отсутствует, игра будет работать нормально, но функции мода могут отсутствовать.)
|
||||
mods.dependency.required=Необходимые зависимости (Необходимо скачивать отдельно, отсутствие может помешать запуску игры.)
|
||||
mods.dependency.tool=Необходимые зависимости (Необходимо скачивать отдельно, отсутствие может помешать запуску игры.)
|
||||
mods.dependency.include=Встроенные зависимости (Уже упакован в файл мода автором. Нет необходимости скачивать отдельно.)
|
||||
mods.dependency.incompatible=Несовместимые моды (Одновременная установка этих модов не позволит запустить игру.)
|
||||
mods.dependency.broken=Сломанные зависимости (Этот мод существовал раньше, но теперь его нет. Попробуйте использовать другой источник скачивания.)
|
||||
mods.disable=Отключить
|
||||
mods.download=Скачать мод
|
||||
mods.download.title=Скачать мод - %1s
|
||||
mods.download.recommend=Рекомендуемая версия мода - Minecraft %1s
|
||||
mods.enable=Включить
|
||||
mods.manage=Управление модами
|
||||
mods.manage=Моды
|
||||
mods.mcbbs=MCBBS
|
||||
mods.mcmod=MCMOD
|
||||
mods.mcmod.page=Страница MCMOD
|
||||
mods.mcmod.search=Искать на MCMOD
|
||||
mods.mcmod=MCMod
|
||||
mods.mcmod.page=Страница MCMod
|
||||
mods.mcmod.search=Искать на MCMod
|
||||
mods.modrinth=Modrinth
|
||||
mods.name=Имя
|
||||
mods.not_modded=Для управления модами необходимо сначала установить загрузчик (Fabric, Forge или LiteLoader).\!
|
||||
mods.restore=Откат
|
||||
mods.name=Название
|
||||
mods.not_modded=Для управления модами необходимо сначала установить загрузчик (Forge, NeoForge, Fabric, Quilt или LiteLoader)!
|
||||
mods.restore=Восстановить
|
||||
mods.url=Официальная страница
|
||||
mods.update_modpack_mod.warning=Обновление модов в пакете модов может привести к непоправимым результатам, возможно, повредить пакет модов так, что он не сможет запуститься. Вы уверены, что хотите обновить?
|
||||
mods.install=Установить
|
||||
mods.save_as=Сохранить как
|
||||
|
||||
datapack=Датапаки
|
||||
nbt.entries=%s записи
|
||||
nbt.open.failed=Не удалось открыть файл
|
||||
nbt.save.failed=Не удалось сохранить файл
|
||||
nbt.title=Смотреть файл - %s
|
||||
|
||||
datapack=Наборы данных
|
||||
datapack.add=Установить
|
||||
datapack.choose_datapack=Выберите датапак для импорта
|
||||
datapack.extension=Датапак
|
||||
datapack.title=Датапаки мира %s
|
||||
datapack.choose_datapack=Выберите набор данных для импорта
|
||||
datapack.extension=Набор данных
|
||||
datapack.title=Мир [%s] - Наборы данных
|
||||
|
||||
web.failed=Не удалось загрузить страницу
|
||||
web.open_in_browser=Хотите ли вы открыть этот адрес в браузере?\n\n%s
|
||||
web.view_in_browser=Смотреть в браузере
|
||||
|
||||
world=Миры
|
||||
world.add=Добавить мир
|
||||
world.datapack=Управление датапаками
|
||||
world.datapack.1_13=Только Minecraft 1.13 или новее поддерживает датапаки.
|
||||
world.description=%s. Последний запуск игры %s. Версия игры\: %s.
|
||||
world.datapack=Управлять наборами данных
|
||||
world.datapack.1_13=Только Minecraft 1.13 или новее поддерживает наборы данных.
|
||||
world.description=%s. | Последний запуск игры %s. | Версия игры\: %s.
|
||||
world.download=Скачать мир
|
||||
world.export=Экспорт мира
|
||||
world.export.title=Выберите папку для экспорта мира
|
||||
@@ -769,50 +845,51 @@ world.export.wizard=Экспорт мира %s
|
||||
world.extension=Архив мира
|
||||
world.game_version=Версия игры
|
||||
world.import.already_exists=Мир уже существует.
|
||||
world.import.choose=Выберите архив сохранения, который хотите импортировать
|
||||
world.import.failed=Не удаётся импортировать этот мир\: %s
|
||||
world.import.invalid=Не удаётся разобрать сохранение.
|
||||
world.info.title=Сведения о мире %s
|
||||
world.import.choose=Выберите архив мира, который хотите импортировать
|
||||
world.import.failed=Не удалось импортировать этот мир\: %s
|
||||
world.import.invalid=Не удалось разобрать мир.
|
||||
world.info.title=Мир [%s] - Сведения
|
||||
world.info.basic=Основные сведения
|
||||
world.info.allow_cheats=Разрешить команды/читы
|
||||
world.info.dimension.the_nether=Нижний мир
|
||||
world.info.dimension.the_end=Край
|
||||
world.info.difficulty=Сложность
|
||||
world.info.difficulty.peaceful=Мирно
|
||||
world.info.difficulty.easy=Легко
|
||||
world.info.difficulty.normal=Обычно
|
||||
world.info.difficulty.hard=Тяжело
|
||||
world.info.difficulty.peaceful=Мирная
|
||||
world.info.difficulty.easy=Лёгкая
|
||||
world.info.difficulty.normal=Нормальная
|
||||
world.info.difficulty.hard=Сложная
|
||||
world.info.failed=Не удалось прочитать сведения о мире
|
||||
world.info.game_version=Версия игры
|
||||
world.info.last_played=Последний запуск игры
|
||||
world.info.generate_features=Генерировать структуры
|
||||
world.info.generate_features=Генерация строений
|
||||
world.info.player=Сведения о игроке
|
||||
world.info.player.food_level=Уровень голода
|
||||
world.info.player.game_type=Режим игры
|
||||
world.info.player.game_type.adventure=Приключение
|
||||
world.info.player.game_type.creative=Творческий
|
||||
world.info.player.game_type.spectator=Наблюдатель
|
||||
world.info.player.game_type.creative=Творчество
|
||||
world.info.player.game_type.spectator=Наблюдение
|
||||
world.info.player.game_type.survival=Выживание
|
||||
world.info.player.health=Здоровье
|
||||
world.info.player.last_death_location=Место последней смерти
|
||||
world.info.player.location=Расположение
|
||||
world.info.player.spawn=Место появления
|
||||
world.info.player.spawn=Точка возрождения
|
||||
world.info.player.xp_level=Уровень опыта
|
||||
world.info.random_seed=Сид
|
||||
world.info.random_seed=Ключ генератора мира
|
||||
world.info.time=Время игры
|
||||
world.info.time.format=%s дн.
|
||||
world.manage=Миры и датапаки
|
||||
world.manage=Миры
|
||||
world.name=Название мира
|
||||
world.name.enter=Введите название мира
|
||||
world.reveal=Показать в проводнике
|
||||
world.reveal=Показать в диспетчере файлов
|
||||
world.show_all=Показать все
|
||||
|
||||
profile=Папки с играми
|
||||
profile.already_exists=Имя уже существует, используйте другое имя.
|
||||
profile.default=Текущая папка
|
||||
profile.home=По умолчанию
|
||||
profile.instance_directory=Папка с экземплярами
|
||||
profile.instance_directory.choose=Выберите папку для экземпляра
|
||||
profile.manage=Список папок с экземплярами
|
||||
profile.home=Лаунчер Minecraft
|
||||
profile.instance_directory=Папка с игрой
|
||||
profile.instance_directory.choose=Выберите папку с игрой
|
||||
profile.manage=Список папок для сборки
|
||||
profile.name=Имя
|
||||
profile.new=Новая папка
|
||||
profile.title=Папки с играми
|
||||
@@ -822,68 +899,77 @@ profile.use_relative_path=По возможности используйте о
|
||||
repositories.custom=Пользовательский репозиторий Maven (%s)
|
||||
repositories.maven_central=Универсальный (Maven Central)
|
||||
repositories.tencentcloud_mirror=Зеркало Mainland China (Репозиторий Tencent Cloud Maven)
|
||||
repositories.chooser=Для работы лаунчера требуется JavaFX.\n\
|
||||
\n\
|
||||
Нажмите «ОК», чтобы скачать JavaFX из указанного репозитория, или нажмите «Отмена» чтобы выйти.\n\
|
||||
\n\
|
||||
Репозитории\:
|
||||
repositories.chooser=Для работы HMCL требуется JavaFX.\n\
|
||||
\n\
|
||||
Нажмите «ОК», чтобы скачать JavaFX из указанного репозитория, или нажмите «Отмена» чтобы выйти.\n\
|
||||
\n\
|
||||
Репозитории:
|
||||
repositories.chooser.title=Выберите зеркало для скачивания JavaFX
|
||||
|
||||
resourcepack=Текстурпаки
|
||||
resourcepack=Пакеты ресурсов
|
||||
|
||||
search=Поиск
|
||||
search.hint.chinese=Поиск на китайском и английском языках
|
||||
search.hint.english=Поиск только на английском языке
|
||||
search.enter=Вводите здесь
|
||||
search.sort=Сортировать
|
||||
search.sort=Сортировка
|
||||
search.first_page=Первый
|
||||
search.previous_page=Пред.
|
||||
search.next_page=След.
|
||||
search.last_page=Последний
|
||||
search.page_n=%d / %s
|
||||
|
||||
selector.choose=Выбрать
|
||||
selector.choose=Выберите
|
||||
selector.choose_file=Выберите файл
|
||||
selector.custom=Свой
|
||||
|
||||
settings=Настройки
|
||||
|
||||
settings.advanced=Расширенные настройки
|
||||
settings.advanced.modify=Изменить расширенные настройки
|
||||
settings.advanced.title=Расширенные настройки - %s
|
||||
settings.advanced.custom_commands=Пользовательские команды
|
||||
settings.advanced.custom_commands.hint=Пользовательские команды выполняются со следующими переменными окружения:\n\
|
||||
\ - $INST_NAME: название версии\n\
|
||||
\ - $INST_ID: название версии\n\
|
||||
\ - $INST_DIR: абсолютный путь к версии\n\
|
||||
\ - $INST_MC_DIR: абсолютный путь к minecraft\n\
|
||||
\ - $INST_JAVA: двоичный файл java, используемый для запуска\n\
|
||||
\ - $INST_FORGE: устанавливается, если установлен Forge\n\
|
||||
\ - $INST_NEOFORGE: устанавливается, если установлен NeoForge\n\
|
||||
\ - $INST_LITELOADER: устанавливается, если установлен LiteLoader\n\
|
||||
\ - $INST_OPTIFINE: устанавливается, если установлен OptiFine\n\
|
||||
\ - $INST_FABRIC: устанавливается, если установлена Fabric\n\
|
||||
\ - $INST_QUILT: устанавливается, если установлена Quilt
|
||||
settings.advanced.dont_check_game_completeness=Не сканировать игровые файлы
|
||||
settings.advanced.dont_check_jvm_validity=Не проверять, может ли JVM запустить игру или нет
|
||||
settings.advanced.game_dir.default=По умолчанию (.minecraft/)
|
||||
settings.advanced.game_dir.independent=Раздельно (.minecraft/versions/<имя экземпляра>/, кроме assets и библиотек)
|
||||
settings.advanced.java_permanent_generation_space=Постоянное пространство
|
||||
\ · $INST_NAME: название сборки.\n\
|
||||
\ · $INST_ID: название сборки.\n\
|
||||
\ · $INST_DIR: абсолютный путь к рабочей папке сборки.\n\
|
||||
\ · $INST_MC_DIR: абсолютный путь к текущей папке игры.\n\
|
||||
\ · $INST_JAVA: двоичный файл java, используемый для запуска.\n\
|
||||
\ · $INST_FORGE: устанавливается, если установлен Forge.\n\
|
||||
\ · $INST_NEOFORGE: устанавливается, если установлен NeoForge.\n\
|
||||
\ · $INST_LITELOADER: устанавливается, если установлен LiteLoader.\n\
|
||||
\ · $INST_OPTIFINE: устанавливается, если установлен OptiFine.\n\
|
||||
\ · $INST_FABRIC: устанавливается, если установлен Fabric.\n\
|
||||
\ · $INST_QUILT: устанавливается, если установлен Quilt.
|
||||
settings.advanced.dont_check_game_completeness=Не проверять целостность игры
|
||||
settings.advanced.dont_check_jvm_validity=Не проверять совместимость Java VM
|
||||
settings.advanced.dont_patch_natives=Не пытайтесь автоматически заменить нативные библиотеки
|
||||
settings.advanced.environment_variables=Переменные среды
|
||||
settings.advanced.game_dir.default=По умолчанию («.minecraft/»)
|
||||
settings.advanced.game_dir.independent=Раздельно («.minecraft/versions/<имя сборки>/», кроме assets и библиотек)
|
||||
settings.advanced.java_permanent_generation_space=Пространство PermGen
|
||||
settings.advanced.java_permanent_generation_space.prompt=в МБ
|
||||
settings.advanced.jvm=Параметры Java Virtual Machine
|
||||
settings.advanced.jvm=Настройки Java VM
|
||||
settings.advanced.jvm_args=Аргументы Java VM
|
||||
settings.advanced.jvm_args.prompt=- Если параметр, введённый в «Параметры Java Virtual Machine», совпадает\nс параметром по умолчанию, он не будет добавлен.\n\
|
||||
- Введите любые параметры GC в «Параметры Java Virtual Machine»,\nпараметр G1 по умолчанию будет отключен.\n\
|
||||
- Нажмите «Не добавлять аргументы JVM по умолчанию» ниже,\nчтобы запустить игру без добавления аргументов по умолчанию.
|
||||
settings.advanced.launcher_visibility.close=Закрывать после запуска игры.
|
||||
settings.advanced.launcher_visibility.hide=Сворачивать после запуска игры.
|
||||
settings.advanced.launcher_visibility.hide_and_reopen=Сворачивать и снова открывать, после закрытия игры.
|
||||
settings.advanced.launcher_visibility.keep=Оставлять видимым.
|
||||
settings.advanced.jvm_args.prompt=\ · Если параметр, введённый в «Аргументы Java VM», совпадает с параметром по умолчанию, он не будет добавлен.\n\
|
||||
\ · Введите любые параметры GC в «Аргументы Java VM», параметр G1 по умолчанию будет отключен.\n\
|
||||
\ · Нажмите «Не добавлять аргументы Java VM по умолчанию» ниже, чтобы запустить игру без добавления аргументов по умолчанию.
|
||||
settings.advanced.launcher_visibility.close=Закрывать после запуска игры
|
||||
settings.advanced.launcher_visibility.hide=Сворачивать после запуска игры
|
||||
settings.advanced.launcher_visibility.hide_and_reopen=Сворачивать и снова открывать, после закрытия игры
|
||||
settings.advanced.launcher_visibility.keep=Оставлять видимым
|
||||
settings.advanced.launcher_visible=Видимость лаунчера
|
||||
settings.advanced.minecraft_arguments=Аргументы запуска
|
||||
settings.advanced.minecraft_arguments.prompt=По умолчанию
|
||||
settings.advanced.natives_directory=Папка с нативными библиотеками
|
||||
settings.advanced.natives_directory.choose=Выберите расоложение нужных нативных библиотек
|
||||
settings.advanced.natives_directory.custom=Своя
|
||||
settings.advanced.natives_directory.custom=Пользовательское
|
||||
settings.advanced.natives_directory.default=По умолчанию
|
||||
settings.advanced.natives_directory.hint=Параметр предназначен только для пользователей Apple M1 или других официально не поддерживаемых платформ. Не изменяйте, если не знаете что делаете.\n\
|
||||
\n\
|
||||
Прежде чем продолжить, убедитесь что все библиотеки (например, lwjgl.dll, libopenal.so) находятся в нужной папке.
|
||||
settings.advanced.no_jvm_args=Не добавлять аргументы JVM по умолчанию
|
||||
settings.advanced.natives_directory.hint=Параметр предназначен только для пользователей Apple silicon или других официально не поддерживаемых платформ. Не изменяйте, если не знаете что делаете.\n\
|
||||
\n\
|
||||
Прежде чем продолжить, убедитесь что все библиотеки (например, lwjgl.dll, libopenal.so) находятся в нужной папке.\n\
|
||||
Примечание: Рекомендуется использовать полностью английские пути для указанных файлов локальных библиотек, иначе игра может не запуститься.
|
||||
settings.advanced.no_jvm_args=Не добавлять аргументы Java VM по умолчанию
|
||||
settings.advanced.precall_command=Команда перед запуском
|
||||
settings.advanced.precall_command.prompt=Команды, которые необходимо выполнить перед запуском игры
|
||||
settings.advanced.process_priority=Приоритет процесса
|
||||
@@ -894,6 +980,11 @@ settings.advanced.process_priority.above_normal=Выше обычного
|
||||
settings.advanced.process_priority.high=Высокий
|
||||
settings.advanced.post_exit_command=Команда после выхода
|
||||
settings.advanced.post_exit_command.prompt=Команды, которые необходимо выполнить после выхода из игры
|
||||
settings.advanced.renderer=Рендерер
|
||||
settings.advanced.renderer.default=OpenGL (По умолчанию)
|
||||
settings.advanced.renderer.d3d12=DirectX 12 (Низкая производительность и совместимость)
|
||||
settings.advanced.renderer.llvmpipe=ПО (Низкая производительность, лучшая совместимость)
|
||||
settings.advanced.renderer.zink=Vulkan (Лучшая производительность, низкая совместимость)
|
||||
settings.advanced.server_ip=Адрес сервера
|
||||
settings.advanced.server_ip.prompt=Присоединяться к серверу при запуске игры
|
||||
settings.advanced.use_native_glfw=[Только для Linux/FreeBSD] Использовать системный GLFW
|
||||
@@ -903,27 +994,30 @@ settings.advanced.workaround.warning=Варианты обхода предна
|
||||
settings.advanced.wrapper_launcher=Команда-оболочка
|
||||
settings.advanced.wrapper_launcher.prompt=Позволяет запускать с помощью дополнительной программы-оболочки, такой как «optirun» в Linux.
|
||||
|
||||
settings.custom=Своё
|
||||
settings.custom=Пользовательское
|
||||
|
||||
settings.game=Игровые настройки
|
||||
settings.game=Настройки
|
||||
settings.game.copy_global=Копировать из глобальные настройки
|
||||
settings.game.copy_global.copy_all=Копировать все
|
||||
settings.game.copy_global.copy_all.confirm=Вы уверены, что хотите переписать текущие настройки сборки? Это действие нельзя отменить!
|
||||
settings.game.current=Игра
|
||||
settings.game.dimension=Разрешение
|
||||
settings.game.exploration=Расположение
|
||||
settings.game.fullscreen=Полноэкранный режим
|
||||
settings.game.java_directory=Путь Java
|
||||
settings.game.java_directory=Java
|
||||
settings.game.java_directory.auto=Автовыбор
|
||||
settings.game.java_directory.auto.not_found=Совместимая версия Java не была установлена.
|
||||
settings.game.java_directory.bit=%s-разрядная
|
||||
settings.game.java_directory.choose=Выбрать папку с Java.
|
||||
settings.game.java_directory.invalid=Неверныая папка Java.
|
||||
settings.game.java_directory.choose=Выбрать папку с Java
|
||||
settings.game.java_directory.invalid=Неверныая папка Java
|
||||
settings.game.java_directory.version=Укажите версию Java
|
||||
settings.game.java_directory.template=%s (%s)
|
||||
settings.game.management=Инструменты
|
||||
settings.game.working_directory=Расположение папки
|
||||
settings.game.working_directory.choose=Выбор расположения
|
||||
settings.game.working_directory.hint=Включите параметр «Раздельно» в разделе «Расположение папки», чтобы позволить текущему экземпляру хранить свои настройки, сохранения и моды в отдельной папке.\n\
|
||||
\n\
|
||||
Рекомендуется включить этот параметр, чтобы избежать конфликтов с модами, но нужно будет переместить свои сохранения вручную.
|
||||
|
||||
\n\
|
||||
Рекомендуется включить этот параметр, чтобы избежать конфликтов с модами, но нужно будет переместить свои сохранения вручную.
|
||||
|
||||
settings.icon=Значок
|
||||
|
||||
@@ -942,7 +1036,7 @@ settings.launcher.font=Шрифт
|
||||
settings.launcher.general=Общие
|
||||
settings.launcher.language=Язык
|
||||
settings.launcher.launcher_log.export=Экспорт логов лаунчера
|
||||
settings.launcher.launcher_log.export.failed=Не удаётся экспортировать логи
|
||||
settings.launcher.launcher_log.export.failed=Не удалось экспортировать логи
|
||||
settings.launcher.launcher_log.export.success=Логи экспортированы в %s
|
||||
settings.launcher.log=Запись логов
|
||||
settings.launcher.log.font=Шрифт
|
||||
@@ -970,15 +1064,15 @@ settings.memory.lower_bound=Мин. объём памяти
|
||||
settings.memory.used_per_total=Использовано %1$.1f ГБ / Всего %2$.1f ГБ
|
||||
settings.physical_memory=Размер физической памяти
|
||||
settings.show_log=Показать логи
|
||||
settings.tabs.installers=Загрузчик модов/OptiFine
|
||||
settings.tabs.installers=Погрузчики
|
||||
settings.take_effect_after_restart=Применится после перезапуска
|
||||
settings.type=Тип настроек экземпляра
|
||||
settings.type.global=Общие настройки экземпляра (совместно используется экземплярами)
|
||||
settings.type.global.manage=Общие настройки игры
|
||||
settings.type.global.edit=Изменить глобальные настройки экземпляра
|
||||
settings.type.special.enable=Включить раздельные настройки для экземпляров
|
||||
settings.type.special.edit=Изменить настройки текущего экземпляра
|
||||
settings.type.special.edit.hint=В текущем экземпляре [%s] включены раздельные настройки для экземпляров, параметры на этой странице НЕ повлияют на этот экземпляр. Нажмите здесь, чтобы изменить раздельные параметры.
|
||||
settings.type.global=Глобальные настройки (совместно используется экземплярами)
|
||||
settings.type.global.manage=Глобальные настройки
|
||||
settings.type.global.edit=Изменить глобальные настройки
|
||||
settings.type.special.enable=Включить раздельные настройки для сборки
|
||||
settings.type.special.edit=Изменить текущие настройки сборки
|
||||
settings.type.special.edit.hint=В текущей сборке «%s» включен параметр «Специфические для сборки настройки». Все настройки на этой странице НЕ влияют на эту сборку. Нажмите здесь, чтобы изменить его настройки.
|
||||
|
||||
sponsor=Спонсоры
|
||||
sponsor.bmclapi=Загрузки для материкового Китая предоставляются BMCLAPI. Нажмите здесь для получения подробностей.
|
||||
@@ -987,6 +1081,8 @@ sponsor.hmcl=Hello Minecraft! Launcher — лаунчер Minecraft с откр
|
||||
system.architecture=Архитектура
|
||||
system.operating_system=Операционная система
|
||||
|
||||
unofficial.hint=Вы используете неофициальную версию HMCL. Мы не можем гарантировать безопасность.
|
||||
|
||||
update=Обновление
|
||||
update.accept=Обновить
|
||||
update.changelog=Изменения
|
||||
@@ -1009,49 +1105,54 @@ update.bubble.title=Доступно обновление: %s
|
||||
update.bubble.subtitle=Кликните здесь для обновления
|
||||
update.note=Предупреждение: Бета-версии могут иметь больше функциональных возможностей и исправлений ошибок, но также и больше возможных ошибок.
|
||||
update.latest=Это последняя версия.
|
||||
update.no_browser=Не удаётся открыть браузер. Ссылка была скопирована в буфер обмена. Вставьте его в адресную строку браузера для обновления.
|
||||
update.no_browser=Не удалось открыть браузер. Ссылка была скопирована в буфер обмена. Вставьте его в адресную строку браузера для обновления.
|
||||
update.tooltip=Обновить
|
||||
|
||||
version=Игры
|
||||
version.name=Название сборки
|
||||
version.cannot_read=Невозможно найти версию игры. Невозможно продолжить автоматическую установку.
|
||||
version.empty=Нет игр
|
||||
version.empty.add=Установить новую версию
|
||||
version.empty=Нет сборок
|
||||
version.empty.add=Установить новую сборок
|
||||
version.empty.launch=Нет версии для запуска, установите версию через страницу скачивания.
|
||||
version.empty.hint=Нет установленной версии игры. Кликните здесь, чтобы скачать новую игру.
|
||||
version.game.old=Старые
|
||||
version.game.old=Исторические
|
||||
version.game.release=Релиз
|
||||
version.game.releases=Релизы
|
||||
version.game.snapshot=Снапшот
|
||||
version.game.snapshots=Снапшоты
|
||||
version.launch=Играть
|
||||
version.launch.test=Тестовая игра
|
||||
version.switch=Сменить версию
|
||||
version.switch=Сменить сборку
|
||||
version.launch_script=Создать сценарий запуска
|
||||
version.launch_script.failed=Невозможно выполнить сценарий запуска.
|
||||
version.launch_script.failed=Невозможно создать сценарий запуска.
|
||||
version.launch_script.save=Сохранить сценарий запуска
|
||||
version.launch_script.success=Создан сценарий %s.
|
||||
version.manage=Все версии
|
||||
version.manage.clean=Очистить каталог игры
|
||||
version.manage.clean.tooltip=Очистить логи, отчёты о сбоях
|
||||
version.manage.duplicate=Скопировать экземпляр игры
|
||||
version.manage.duplicate.duplicate_save=Скопировать сохранения
|
||||
version.manage.duplicate.prompt=Введите название новой версии
|
||||
version.manage=Все сборки
|
||||
version.manage.clean=Очистить журнал игры
|
||||
version.manage.clean.tooltip=Очистить файлы в папках «logs» и «crash-reports».
|
||||
version.manage.duplicate=Скопировать экземпляр сборки
|
||||
version.manage.duplicate.duplicate_save=Скопировать миры
|
||||
version.manage.duplicate.prompt=Введите новое название для этой сборки
|
||||
version.manage.duplicate.confirm=Новая версия-копия, будет заблокирована: принудительный независимый режим, независимые игровые настройки.
|
||||
version.manage.manage=Управление версией
|
||||
version.manage.manage.title=Управление версией - %1s
|
||||
version.manage.manage=Изменить сборку
|
||||
version.manage.manage.title=Изменить сборку - %1s
|
||||
version.manage.redownload_assets_index=Обновить файлы игровых активов
|
||||
version.manage.remove=Удалить
|
||||
version.manage.remove.confirm=Удалить %s? Вы не сможете восстановить эту версию снова!
|
||||
version.manage.remove.confirm.trash=Удалить %s? Вы можете восстановить эту версию с именем %s из корзины системы.
|
||||
version.manage.remove.confirm.independent=Поскольку эта версия находится в независимом режиме, удаление этой версии также приведёт к удалению всех сохранённых миров, принадлежащих этой версии. Удалить %s?
|
||||
version.manage.remove.confirm=Удалить %s? Вы не сможете восстановить эту сборку снова!
|
||||
version.manage.remove.confirm.trash=Удалить %s? Вы можете восстановить эту сборку с именем %s из корзины системы.
|
||||
version.manage.remove.confirm.independent=Поскольку эта сборка находится в режиме разделения, удаление этой сборки приведет к удалению всех миров, принадлежащих этой сборке. Удалить %s?
|
||||
version.manage.remove_assets=Удалить файлы игровых активов
|
||||
version.manage.remove_libraries=Удалить файлы библиотек
|
||||
version.manage.rename=Переименовать
|
||||
version.manage.rename.message=Введите новое название для этой версии
|
||||
version.manage.rename.fail=Не удалось переименовать версию.
|
||||
version.manage.rename.message=Введите новое название для этой сборки
|
||||
version.manage.rename.fail=Не удалось переименовать сборку. Возможно, некоторые файлы уже используются или в их имени содержится недопустимый символ.
|
||||
version.settings=Настройки
|
||||
version.update=Обновить модпак
|
||||
|
||||
wiki.tooltip=Страница Minecraft Wiki
|
||||
wiki.version.game.release=https://ru.minecraft.wiki/w/%s_(Java_Edition)
|
||||
wiki.version.game.snapshot=https://ru.minecraft.wiki/w/%s_(Java_Edition)
|
||||
|
||||
wizard.prev=< Пред.
|
||||
wizard.failed=Не удалось
|
||||
wizard.finish=Завершено
|
||||
|
||||
@@ -84,7 +84,7 @@ public final class MicrosoftAccount extends OAuthAccount {
|
||||
|
||||
@Override
|
||||
public AuthInfo logIn() throws AuthenticationException {
|
||||
if (!authenticated) {
|
||||
if (!authenticated || System.currentTimeMillis() > session.getNotAfter()) {
|
||||
if (service.validate(session.getNotAfter(), session.getTokenType(), session.getAccessToken())) {
|
||||
authenticated = true;
|
||||
} else {
|
||||
|
||||
Reference in new issue
Block a user