Compare commits
51
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 | ||
|
|
881b0e9127 | ||
|
|
77e06c8820 | ||
|
|
bc2405cf12 | ||
|
|
72b5428f8b | ||
|
|
e7e8d45497 | ||
|
|
82dbe18d78 | ||
|
|
ecb7ef0867 | ||
|
|
d40742a624 | ||
|
|
6f8002a0cf | ||
|
|
3d3896c5c4 | ||
|
|
a24f8e2ec0 | ||
|
|
fce08a6923 | ||
|
|
5fa01c8838 | ||
|
|
15a3fb5f59 | ||
|
|
d5ab14f476 | ||
|
|
554ecfd6d5 | ||
|
|
c2abe755f4 | ||
|
|
c0410c46ff | ||
|
|
da93b4c12d | ||
|
|
c16d6e8d5f | ||
|
|
6df73aeb68 | ||
|
|
23d11eca81 | ||
|
|
0e307f3b71 |
No files matched your search
@@ -225,8 +225,11 @@ public final class Launcher extends Application {
|
||||
LOG.info("Operating System: " + (OperatingSystem.OS_RELEASE_PRETTY_NAME == null
|
||||
? OperatingSystem.SYSTEM_NAME + ' ' + OperatingSystem.SYSTEM_VERSION
|
||||
: OperatingSystem.OS_RELEASE_PRETTY_NAME + " (" + OperatingSystem.SYSTEM_NAME + ' ' + OperatingSystem.SYSTEM_VERSION + ')'));
|
||||
LOG.info("System Architecture: " + Architecture.SYSTEM_ARCH_NAME);
|
||||
LOG.info("Java Architecture: " + Architecture.CURRENT_ARCH_NAME);
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
LOG.info("Processor Identifier: " + System.getenv("PROCESSOR_IDENTIFIER"));
|
||||
}
|
||||
LOG.info("System Architecture: " + Architecture.SYSTEM_ARCH.getDisplayName());
|
||||
LOG.info("Java Architecture: " + Architecture.CURRENT_ARCH.getDisplayName());
|
||||
LOG.info("Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor"));
|
||||
LOG.info("Java VM Version: " + System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.info") + "), " + System.getProperty("java.vm.vendor"));
|
||||
LOG.info("Java Home: " + System.getProperty("java.home"));
|
||||
@@ -245,7 +248,6 @@ public final class Launcher extends Application {
|
||||
LOG.info("XDG Session Type: " + System.getenv("XDG_SESSION_TYPE"));
|
||||
LOG.info("XDG Current Desktop: " + System.getenv("XDG_CURRENT_DESKTOP"));
|
||||
}
|
||||
|
||||
launch(Launcher.class, args);
|
||||
} catch (Throwable e) { // Fucking JavaFX will suppress the exception and will break our crash reporter.
|
||||
CRASH_REPORTER.uncaughtException(Thread.currentThread(), e);
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -43,8 +43,8 @@ public class CrashReport {
|
||||
stackTrace + "\n\n" +
|
||||
"-- System Details --\n" +
|
||||
" Operating System: " + OperatingSystem.SYSTEM_NAME + ' ' + OperatingSystem.SYSTEM_VERSION + "\n" +
|
||||
" System Architecture: " + Architecture.SYSTEM_ARCH_NAME + "\n" +
|
||||
" Java Architecture: " + Architecture.CURRENT_ARCH_NAME + "\n" +
|
||||
" System Architecture: " + Architecture.SYSTEM_ARCH.getDisplayName() + "\n" +
|
||||
" Java Architecture: " + Architecture.CURRENT_ARCH.getDisplayName() + "\n" +
|
||||
" Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor") + "\n" +
|
||||
" Java VM Version: " + System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.info") + "), " + System.getProperty("java.vm.vendor") + "\n" +
|
||||
" JVM Max Memory: " + Runtime.getRuntime().maxMemory() + "\n" +
|
||||
|
||||
@@ -38,7 +38,6 @@ import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.PropertyUtils;
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
@@ -359,12 +358,9 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
vs = createLocalVersionSetting(id);
|
||||
if (vs == null)
|
||||
return null;
|
||||
VersionIconType versionIcon = vs.getVersionIcon();
|
||||
if (vs.isUsesGlobal()) {
|
||||
PropertyUtils.copyProperties(profile.getGlobal(), vs);
|
||||
vs.setUsesGlobal(false);
|
||||
}
|
||||
vs.setVersionIcon(versionIcon); // versionIcon is preserved
|
||||
return vs;
|
||||
}
|
||||
|
||||
@@ -374,7 +370,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
vs.setUsesGlobal(true);
|
||||
}
|
||||
|
||||
public LaunchOptions getLaunchOptions(String version, JavaRuntime javaVersion, File gameDir, List<String> javaAgents, boolean makeLaunchScript) {
|
||||
public LaunchOptions getLaunchOptions(String version, JavaRuntime javaVersion, File gameDir, List<String> javaAgents, List<String> javaArguments, boolean makeLaunchScript) {
|
||||
VersionSetting vs = getVersionSetting(version);
|
||||
|
||||
LaunchOptions.Builder builder = new LaunchOptions.Builder()
|
||||
@@ -417,7 +413,8 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
.setUseNativeGLFW(vs.isUseNativeGLFW())
|
||||
.setUseNativeOpenAL(vs.isUseNativeOpenAL())
|
||||
.setDaemon(!makeLaunchScript && vs.getLauncherVisibility().isDaemon())
|
||||
.setJavaAgents(javaAgents);
|
||||
.setJavaAgents(javaAgents)
|
||||
.setJavaArguments(javaArguments);
|
||||
if (config().hasProxy()) {
|
||||
builder.setProxy(ProxyManager.getProxy());
|
||||
if (config().hasProxyAuth()) {
|
||||
|
||||
@@ -131,13 +131,14 @@ public final class LauncherHelper {
|
||||
boolean integrityCheck = repository.unmarkVersionLaunchedAbnormally(selectedVersion);
|
||||
CountDownLatch launchingLatch = new CountDownLatch(1);
|
||||
List<String> javaAgents = new ArrayList<>(0);
|
||||
List<String> javaArguments = new ArrayList<>(0);
|
||||
|
||||
AtomicReference<JavaRuntime> javaVersionRef = new AtomicReference<>();
|
||||
|
||||
TaskExecutor executor = checkGameState(profile, setting, version.get())
|
||||
.thenComposeAsync(java -> {
|
||||
javaVersionRef.set(Objects.requireNonNull(java));
|
||||
version.set(NativePatcher.patchNative(version.get(), gameVersion.orElse(null), java, setting));
|
||||
version.set(NativePatcher.patchNative(repository, version.get(), gameVersion.orElse(null), java, setting, javaArguments));
|
||||
if (setting.isNotCheckGame())
|
||||
return null;
|
||||
return Task.allOf(
|
||||
@@ -155,7 +156,7 @@ public final class LauncherHelper {
|
||||
Task.composeAsync(() -> {
|
||||
Renderer renderer = setting.getRenderer();
|
||||
if (renderer != Renderer.DEFAULT && OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
Library lib = NativePatcher.getMesaLoader(java, renderer);
|
||||
Library lib = NativePatcher.getMesaLoader(java);
|
||||
if (lib == null)
|
||||
return null;
|
||||
File file = dependencyManager.getGameRepository().getLibraryFile(version.get(), lib);
|
||||
@@ -182,7 +183,8 @@ public final class LauncherHelper {
|
||||
.thenComposeAsync(() -> gameVersion.map(s -> new GameVerificationFixTask(dependencyManager, s, version.get())).orElse(null))
|
||||
.thenComposeAsync(() -> logIn(account).withStage("launch.state.logging_in"))
|
||||
.thenComposeAsync(authInfo -> Task.supplyAsync(() -> {
|
||||
LaunchOptions launchOptions = repository.getLaunchOptions(selectedVersion, javaVersionRef.get(), profile.getGameDir(), javaAgents, scriptFile != null);
|
||||
LaunchOptions launchOptions = repository.getLaunchOptions(
|
||||
selectedVersion, javaVersionRef.get(), profile.getGameDir(), javaAgents, javaArguments, scriptFile != null);
|
||||
|
||||
LOG.info("Here's the structure of game mod directory:\n" + FileUtils.printFileStructure(repository.getModManager(selectedVersion).getModsDirectory(), 10));
|
||||
|
||||
|
||||
@@ -376,8 +376,8 @@ public final class JavaManager {
|
||||
searchAllJavaInDirectory(javaRuntimes, Paths.get(System.getProperty("user.home"), "/.sdkman/candidates/java")); // SDKMAN!
|
||||
break;
|
||||
case OSX:
|
||||
tryAddJavaHome(javaRuntimes, Paths.get("/Library/Java/JavaVirtualMachines/Contents/Home"));
|
||||
tryAddJavaHome(javaRuntimes, Paths.get(System.getProperty("user.home"), "/Library/Java/JavaVirtualMachines/Contents/Home"));
|
||||
searchJavaInMacJavaVirtualMachines(javaRuntimes, Paths.get("/Library/Java/JavaVirtualMachines"));
|
||||
searchJavaInMacJavaVirtualMachines(javaRuntimes, Paths.get(System.getProperty("user.home"), "/Library/Java/JavaVirtualMachines"));
|
||||
tryAddJavaExecutable(javaRuntimes, Paths.get("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java"));
|
||||
tryAddJavaExecutable(javaRuntimes, Paths.get("/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/MacOS/itms/java/bin/java"));
|
||||
// Homebrew
|
||||
@@ -631,6 +631,20 @@ public final class JavaManager {
|
||||
}
|
||||
}
|
||||
|
||||
private static void searchJavaInMacJavaVirtualMachines(Map<Path, JavaRuntime> javaRuntimes, Path directory) {
|
||||
if (!Files.isDirectory(directory)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory)) {
|
||||
for (Path subDir : stream) {
|
||||
tryAddJavaHome(javaRuntimes, subDir.resolve("Contents/Home"));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to find Java in " + directory, e);
|
||||
}
|
||||
}
|
||||
|
||||
// ==== Windows Registry Support ====
|
||||
private static void queryJavaInRegistryKey(Map<Path, JavaRuntime> javaRuntimes, String location) {
|
||||
for (String java : querySubFolders(location)) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -24,13 +24,13 @@ import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.WeakInvalidationListener;
|
||||
import javafx.beans.WeakListener;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.value.*;
|
||||
import javafx.event.Event;
|
||||
import javafx.event.EventDispatcher;
|
||||
import javafx.event.EventType;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
@@ -79,6 +79,7 @@ import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import java.io.*;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.net.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -253,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) {
|
||||
@@ -582,7 +569,7 @@ public final class FXUtils {
|
||||
focusedListener = (observable, oldFocused, newFocused) -> {
|
||||
if (oldFocused && !newFocused) {
|
||||
if (textField.validate()) {
|
||||
uppdateProperty();
|
||||
updateProperty();
|
||||
} else {
|
||||
// Rollback to old value
|
||||
updateTextField();
|
||||
@@ -594,7 +581,7 @@ public final class FXUtils {
|
||||
if (oldScene != null && newScene == null) {
|
||||
// Component is being removed from scene
|
||||
if (textField.validate()) {
|
||||
uppdateProperty();
|
||||
updateProperty();
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -604,7 +591,7 @@ public final class FXUtils {
|
||||
};
|
||||
}
|
||||
|
||||
public void uppdateProperty() {
|
||||
public void updateProperty() {
|
||||
String newText = textField.getText();
|
||||
@SuppressWarnings("unchecked")
|
||||
T newValue = converter == null ? (T) newText : converter.fromString(newText);
|
||||
@@ -620,20 +607,80 @@ public final class FXUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void bindBoolean(JFXToggleButton toggleButton, Property<Boolean> property) {
|
||||
toggleButton.selectedProperty().bindBidirectional(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;
|
||||
private final int hashCode;
|
||||
|
||||
public static void unbindBoolean(JFXToggleButton toggleButton, Property<Boolean> property) {
|
||||
toggleButton.selectedProperty().unbindBidirectional(property);
|
||||
}
|
||||
private boolean updating = false;
|
||||
|
||||
public static void bindBoolean(JFXCheckBox checkBox, Property<Boolean> property) {
|
||||
checkBox.selectedProperty().bindBidirectional(property);
|
||||
}
|
||||
private EnumBidirectionalBinding(JFXComboBox<E> comboBox, Property<E> property) {
|
||||
this.comboBoxRef = new WeakReference<>(comboBox);
|
||||
this.propertyRef = new WeakReference<>(property);
|
||||
this.hashCode = System.identityHashCode(comboBox) ^ System.identityHashCode(property);
|
||||
}
|
||||
|
||||
public static void unbindBoolean(JFXCheckBox checkBox, Property<Boolean> property) {
|
||||
checkBox.selectedProperty().unbindBidirectional(property);
|
||||
@Override
|
||||
public void invalidated(Observable sourceProperty) {
|
||||
if (!updating) {
|
||||
final JFXComboBox<E> comboBox = comboBoxRef.get();
|
||||
final Property<E> property = propertyRef.get();
|
||||
|
||||
if (comboBox == null || property == null) {
|
||||
if (comboBox != null) {
|
||||
comboBox.getSelectionModel().selectedItemProperty().removeListener(this);
|
||||
}
|
||||
|
||||
if (property != null) {
|
||||
property.removeListener(this);
|
||||
}
|
||||
} else {
|
||||
updating = true;
|
||||
try {
|
||||
if (property == sourceProperty) {
|
||||
E newValue = property.getValue();
|
||||
comboBox.getSelectionModel().select(newValue);
|
||||
} else {
|
||||
E newValue = comboBox.getSelectionModel().getSelectedItem();
|
||||
property.setValue(newValue);
|
||||
}
|
||||
} finally {
|
||||
updating = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean wasGarbageCollected() {
|
||||
return comboBoxRef.get() == null || propertyRef.get() == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (!(o instanceof EnumBidirectionalBinding))
|
||||
return false;
|
||||
|
||||
EnumBidirectionalBinding<?> that = (EnumBidirectionalBinding<?>) o;
|
||||
|
||||
final JFXComboBox<E> comboBox = this.comboBoxRef.get();
|
||||
final Property<E> property = this.propertyRef.get();
|
||||
|
||||
final JFXComboBox<?> thatComboBox = that.comboBoxRef.get();
|
||||
final Property<?> thatProperty = that.propertyRef.get();
|
||||
|
||||
if (comboBox == null || property == null || thatComboBox == null || thatProperty == null)
|
||||
return false;
|
||||
|
||||
return comboBox == thatComboBox && property == thatProperty;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -642,20 +689,18 @@ public final class FXUtils {
|
||||
*
|
||||
* @param comboBox the combo box being bound with {@code property}.
|
||||
* @param property the property being bound with {@code combo box}.
|
||||
* @see #unbindEnum(JFXComboBox)
|
||||
* @see #unbindEnum(JFXComboBox, Property)
|
||||
* @see ExtendedProperties#selectedItemPropertyFor(ComboBox)
|
||||
*/
|
||||
public static <T extends Enum<T>> void bindEnum(JFXComboBox<T> comboBox, Property<T> property) {
|
||||
unbindEnum(comboBox);
|
||||
EnumBidirectionalBinding<T> binding = new EnumBidirectionalBinding<>(comboBox, property);
|
||||
|
||||
T currentValue = property.getValue();
|
||||
@SuppressWarnings("unchecked")
|
||||
T[] enumConstants = (T[]) currentValue.getClass().getEnumConstants();
|
||||
ChangeListener<Number> listener = (a, b, newValue) -> property.setValue(enumConstants[newValue.intValue()]);
|
||||
comboBox.getSelectionModel().selectedItemProperty().removeListener(binding);
|
||||
property.removeListener(binding);
|
||||
|
||||
comboBox.getSelectionModel().select(currentValue.ordinal());
|
||||
comboBox.getProperties().put("FXUtils.bindEnum.listener", listener);
|
||||
comboBox.getSelectionModel().selectedIndexProperty().addListener(listener);
|
||||
comboBox.getSelectionModel().select(property.getValue());
|
||||
comboBox.getSelectionModel().selectedItemProperty().addListener(binding);
|
||||
property.addListener(binding);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -666,11 +711,10 @@ public final class FXUtils {
|
||||
* @see #bindEnum(JFXComboBox, Property)
|
||||
* @see ExtendedProperties#selectedItemPropertyFor(ComboBox)
|
||||
*/
|
||||
public static void unbindEnum(JFXComboBox<? extends Enum<?>> comboBox) {
|
||||
@SuppressWarnings("unchecked")
|
||||
ChangeListener<Number> listener = (ChangeListener<Number>) comboBox.getProperties().remove("FXUtils.bindEnum.listener");
|
||||
if (listener != null)
|
||||
comboBox.getSelectionModel().selectedIndexProperty().removeListener(listener);
|
||||
public static <T extends Enum<T>> void unbindEnum(JFXComboBox<T> comboBox, Property<T> property) {
|
||||
EnumBidirectionalBinding<T> binding = new EnumBidirectionalBinding<>(comboBox, property);
|
||||
comboBox.getSelectionModel().selectedItemProperty().removeListener(binding);
|
||||
property.removeListener(binding);
|
||||
}
|
||||
|
||||
public static void bindAllEnabled(BooleanProperty allEnabled, BooleanProperty... children) {
|
||||
@@ -1010,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) {
|
||||
@@ -1028,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"));
|
||||
@@ -108,10 +111,10 @@ public class OfflineAccountSkinPane extends StackPane {
|
||||
|
||||
skinItem.loadChildren(Arrays.asList(
|
||||
new MultiFileItem.Option<>(i18n("message.default"), Skin.Type.DEFAULT),
|
||||
new MultiFileItem.Option<>("Steve", Skin.Type.STEVE),
|
||||
new MultiFileItem.Option<>("Alex", Skin.Type.ALEX),
|
||||
new MultiFileItem.Option<>(i18n("account.skin.type.steve"), Skin.Type.STEVE),
|
||||
new MultiFileItem.Option<>(i18n("account.skin.type.alex"), Skin.Type.ALEX),
|
||||
new MultiFileItem.Option<>(i18n("account.skin.type.local_file"), Skin.Type.LOCAL_FILE),
|
||||
new MultiFileItem.Option<>("LittleSkin", Skin.Type.LITTLE_SKIN),
|
||||
new MultiFileItem.Option<>(i18n("account.skin.type.little_skin"), Skin.Type.LITTLE_SKIN),
|
||||
new MultiFileItem.Option<>(i18n("account.skin.type.csl_api"), Skin.Type.CUSTOM_SKIN_LOADER_API)
|
||||
));
|
||||
|
||||
@@ -201,6 +204,12 @@ public class OfflineAccountSkinPane extends StackPane {
|
||||
}
|
||||
|
||||
private Skin getSkin() {
|
||||
return new Skin(skinItem.getSelectedData(), cslApiField.getText(), modelCombobox.getValue(), skinSelector.getValue(), capeSelector.getValue());
|
||||
Skin.Type type = skinItem.getSelectedData();
|
||||
if (type == Skin.Type.LOCAL_FILE) {
|
||||
return new Skin(type, cslApiField.getText(), modelCombobox.getValue(), skinSelector.getValue(), capeSelector.getValue());
|
||||
} else {
|
||||
String cslApi = type == Skin.Type.CUSTOM_SKIN_LOADER_API ? cslApiField.getText() : null;
|
||||
return new Skin(type, cslApi, null, null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -24,6 +24,8 @@ import javafx.scene.layout.Pane;
|
||||
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.ui.versions.VersionPage;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -54,11 +56,36 @@ public class AdvancedListBox extends ScrollPane {
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdvancedListBox addNavigationDrawerItem(Consumer<AdvancedListItem> fn) {
|
||||
private AdvancedListItem createNavigationDrawerItem(String title, SVG leftGraphic) {
|
||||
AdvancedListItem item = new AdvancedListItem();
|
||||
item.getStyleClass().add("navigation-drawer-item");
|
||||
item.setActionButtonVisible(false);
|
||||
fn.accept(item);
|
||||
item.setTitle(title);
|
||||
if (leftGraphic != null) {
|
||||
item.setLeftGraphic(VersionPage.wrap(leftGraphic));
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
public AdvancedListBox addNavigationDrawerItem(String title, SVG leftGraphic, Runnable onAction) {
|
||||
return addNavigationDrawerItem(title, leftGraphic, onAction, null);
|
||||
}
|
||||
|
||||
public AdvancedListBox addNavigationDrawerItem(String title, SVG leftGraphic, Runnable onAction, Consumer<AdvancedListItem> initializer) {
|
||||
AdvancedListItem item = createNavigationDrawerItem(title, leftGraphic);
|
||||
if (onAction != null) {
|
||||
item.setOnAction(e -> onAction.run());
|
||||
}
|
||||
if (initializer != null) {
|
||||
initializer.accept(item);
|
||||
}
|
||||
return add(item);
|
||||
}
|
||||
|
||||
public AdvancedListBox addNavigationDrawerTab(TabHeader tabHeader, TabControl.Tab<?> tab, String title, SVG leftGraphic) {
|
||||
AdvancedListItem item = createNavigationDrawerItem(title, leftGraphic);
|
||||
item.activeProperty().bind(tabHeader.getSelectionModel().selectedItemProperty().isEqualTo(tab));
|
||||
item.setOnAction(e -> tabHeader.select(tab));
|
||||
return add(item);
|
||||
}
|
||||
|
||||
|
||||
+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(() -> {
|
||||
|
||||
@@ -60,7 +60,6 @@ import java.util.concurrent.CancellationException;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
@@ -100,43 +99,16 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE);
|
||||
});
|
||||
|
||||
{
|
||||
AdvancedListBox sideBar = new AdvancedListBox()
|
||||
.startCategory(i18n("download.game").toUpperCase(Locale.ROOT))
|
||||
.addNavigationDrawerItem(item -> {
|
||||
item.setTitle(i18n("game"));
|
||||
item.setLeftGraphic(wrap(SVG.GAMEPAD));
|
||||
item.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(newGameTab));
|
||||
item.setOnAction(e -> tab.select(newGameTab));
|
||||
})
|
||||
.addNavigationDrawerItem(settingsItem -> {
|
||||
settingsItem.setTitle(i18n("modpack"));
|
||||
settingsItem.setLeftGraphic(wrap(SVG.PACK));
|
||||
settingsItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(modpackTab));
|
||||
settingsItem.setOnAction(e -> tab.select(modpackTab));
|
||||
})
|
||||
.startCategory(i18n("download.content").toUpperCase(Locale.ROOT))
|
||||
.addNavigationDrawerItem(item -> {
|
||||
item.setTitle(i18n("mods"));
|
||||
item.setLeftGraphic(wrap(SVG.PUZZLE));
|
||||
item.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(modTab));
|
||||
item.setOnAction(e -> tab.select(modTab));
|
||||
})
|
||||
.addNavigationDrawerItem(item -> {
|
||||
item.setTitle(i18n("resourcepack"));
|
||||
item.setLeftGraphic(wrap(SVG.TEXTURE_BOX));
|
||||
item.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(resourcePackTab));
|
||||
item.setOnAction(e -> tab.select(resourcePackTab));
|
||||
})
|
||||
.addNavigationDrawerItem(item -> {
|
||||
item.setTitle(i18n("world"));
|
||||
item.setLeftGraphic(wrap(SVG.EARTH));
|
||||
item.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(worldTab));
|
||||
item.setOnAction(e -> selectTabIfCurseForgeAvailable(worldTab));
|
||||
});
|
||||
FXUtils.setLimitWidth(sideBar, 200);
|
||||
setLeft(sideBar);
|
||||
}
|
||||
AdvancedListBox sideBar = new AdvancedListBox()
|
||||
.startCategory(i18n("download.game").toUpperCase(Locale.ROOT))
|
||||
.addNavigationDrawerTab(tab, newGameTab, i18n("game"), SVG.GAMEPAD)
|
||||
.addNavigationDrawerTab(tab, modpackTab, i18n("modpack"), SVG.PACK)
|
||||
.startCategory(i18n("download.content").toUpperCase(Locale.ROOT))
|
||||
.addNavigationDrawerTab(tab, modTab, i18n("mods"), SVG.PUZZLE)
|
||||
.addNavigationDrawerTab(tab, resourcePackTab, i18n("resourcepack"), SVG.TEXTURE_BOX)
|
||||
.addNavigationDrawerTab(tab, worldTab, i18n("world"), SVG.EARTH);
|
||||
FXUtils.setLimitWidth(sideBar, 200);
|
||||
setLeft(sideBar);
|
||||
|
||||
setCenter(transitionPane);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ import com.jfoenix.controls.JFXTextField;
|
||||
import javafx.animation.PauseTransition;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
@@ -59,6 +61,8 @@ import org.jackhuang.hmcl.ui.wizard.Navigation;
|
||||
import org.jackhuang.hmcl.ui.wizard.Refreshable;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.Holder;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -72,9 +76,7 @@ import static org.jackhuang.hmcl.ui.FXUtils.ignoreEvent;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
|
||||
import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.wrap;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.formatDateTime;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.StringUtils.isBlank;
|
||||
|
||||
public final class VersionsPage extends BorderPane implements WizardPage, Refreshable {
|
||||
private final String gameVersion;
|
||||
@@ -90,18 +92,14 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
private final JFXCheckBox chkRelease;
|
||||
private final JFXCheckBox chkSnapshot;
|
||||
private final JFXCheckBox chkOld;
|
||||
private final JFXButton btnRefresh;
|
||||
private final HBox checkPane;
|
||||
private final ComponentList centrePane;
|
||||
private final StackPane center;
|
||||
|
||||
private final VersionList<?> versionList;
|
||||
private CompletableFuture<?> executor;
|
||||
|
||||
private final TransitionPane toolbarPane;
|
||||
private final HBox searchBar;
|
||||
private final JFXTextField searchField;
|
||||
private boolean isSearching = false;
|
||||
private final StringProperty queryString = new SimpleStringProperty();
|
||||
|
||||
public VersionsPage(Navigation navigation, String title, String gameVersion, DownloadProvider downloadProvider, String libraryId, Runnable callback) {
|
||||
this.title = title;
|
||||
@@ -117,6 +115,8 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
this.setTop(hintPane);
|
||||
|
||||
root = new TransitionPane();
|
||||
BorderPane toolbarPane = new BorderPane();
|
||||
JFXButton btnRefresh;
|
||||
{
|
||||
spinner = new JFXSpinner();
|
||||
|
||||
@@ -126,7 +126,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
centrePane = new ComponentList();
|
||||
centrePane.getStyleClass().add("no-padding");
|
||||
{
|
||||
checkPane = new HBox();
|
||||
HBox checkPane = new HBox();
|
||||
checkPane.setSpacing(10);
|
||||
{
|
||||
chkRelease = new JFXCheckBox(i18n("version.game.releases"));
|
||||
@@ -139,24 +139,69 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
chkOld = new JFXCheckBox(i18n("version.game.old"));
|
||||
HBox.setMargin(chkOld, new Insets(10, 0, 10, 0));
|
||||
|
||||
HBox pane = new HBox();
|
||||
HBox.setHgrow(pane, Priority.ALWAYS);
|
||||
|
||||
btnRefresh = new JFXButton(i18n("button.refresh"));
|
||||
btnRefresh.getStyleClass().add("jfx-tool-bar-button");
|
||||
btnRefresh.setOnAction(e -> onRefresh());
|
||||
|
||||
checkPane.getChildren().setAll(chkRelease, chkSnapshot, chkOld, pane, btnRefresh);
|
||||
checkPane.getChildren().setAll(chkRelease, chkSnapshot, chkOld);
|
||||
}
|
||||
|
||||
list = new JFXListView<>();
|
||||
list.getStyleClass().add("jfx-list-view-float");
|
||||
VBox.setVgrow(list, Priority.ALWAYS);
|
||||
|
||||
TransitionPane rightToolbarPane = new TransitionPane();
|
||||
{
|
||||
HBox refreshPane = new HBox();
|
||||
refreshPane.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
btnRefresh = new JFXButton(i18n("button.refresh"));
|
||||
btnRefresh.getStyleClass().add("jfx-tool-bar-button");
|
||||
btnRefresh.setOnAction(e -> onRefresh());
|
||||
|
||||
JFXButton btnSearch = new JFXButton(i18n("search"));
|
||||
btnSearch.getStyleClass().add("jfx-tool-bar-button");
|
||||
btnSearch.setGraphic(wrap(SVG.MAGNIFY.createIcon(Theme.blackFill(), -1, -1)));
|
||||
|
||||
searchBar = new HBox();
|
||||
{
|
||||
searchBar.setAlignment(Pos.CENTER);
|
||||
searchBar.setPadding(new Insets(0, 5, 0, 0));
|
||||
|
||||
JFXTextField searchField = new JFXTextField();
|
||||
searchField.setPromptText(i18n("search"));
|
||||
HBox.setHgrow(searchField, Priority.ALWAYS);
|
||||
|
||||
JFXButton closeSearchBar = new JFXButton();
|
||||
closeSearchBar.getStyleClass().add("jfx-tool-bar-button");
|
||||
closeSearchBar.setGraphic(wrap(SVG.CLOSE.createIcon(Theme.blackFill(), -1, -1)));
|
||||
closeSearchBar.setOnAction(e -> {
|
||||
searchField.clear();
|
||||
rightToolbarPane.setContent(refreshPane, ContainerAnimations.FADE);
|
||||
});
|
||||
onEscPressed(searchField, closeSearchBar::fire);
|
||||
PauseTransition pause = new PauseTransition(Duration.millis(100));
|
||||
pause.setOnFinished(e -> queryString.set(searchField.getText()));
|
||||
searchField.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
pause.setRate(1);
|
||||
pause.playFromStart();
|
||||
});
|
||||
|
||||
searchBar.getChildren().setAll(searchField, closeSearchBar);
|
||||
|
||||
btnSearch.setOnAction(e -> {
|
||||
rightToolbarPane.setContent(searchBar, ContainerAnimations.FADE);
|
||||
searchField.requestFocus();
|
||||
});
|
||||
}
|
||||
|
||||
refreshPane.getChildren().setAll(new HBox(btnSearch, btnRefresh));
|
||||
rightToolbarPane.setContent(refreshPane, ContainerAnimations.NONE);
|
||||
}
|
||||
|
||||
// ListViewBehavior would consume ESC pressed event, preventing us from handling it, so we ignore it here
|
||||
ignoreEvent(list, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE);
|
||||
|
||||
centrePane.getContent().setAll(checkPane, list);
|
||||
toolbarPane.setLeft(checkPane);
|
||||
toolbarPane.setRight(rightToolbarPane);
|
||||
|
||||
centrePane.getContent().setAll(toolbarPane, list);
|
||||
}
|
||||
|
||||
center.getChildren().setAll(centrePane);
|
||||
@@ -192,16 +237,39 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
chkOld.setVisible(hasType);
|
||||
|
||||
if (hasType) {
|
||||
centrePane.getContent().setAll(checkPane, list);
|
||||
centrePane.getContent().setAll(toolbarPane, list);
|
||||
} else {
|
||||
centrePane.getContent().setAll(list);
|
||||
}
|
||||
ComponentList.setVgrow(list, Priority.ALWAYS);
|
||||
|
||||
InvalidationListener listener = o -> list.getItems().setAll(loadVersions());
|
||||
InvalidationListener listener = o -> {
|
||||
List<RemoteVersion> versions = loadVersions();
|
||||
String query = queryString.get();
|
||||
if (!StringUtils.isBlank(query)) {
|
||||
Predicate<RemoteVersion> predicate;
|
||||
if (query.startsWith("regex:")) {
|
||||
try {
|
||||
Pattern pattern = Pattern.compile(query.substring("regex:".length()));
|
||||
predicate = it -> pattern.matcher(it.getSelfVersion()).find();
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Illegal regular expression", e);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
String lowerQueryString = query.toLowerCase(Locale.ROOT);
|
||||
predicate = it -> it.getSelfVersion().toLowerCase(Locale.ROOT).contains(lowerQueryString);
|
||||
}
|
||||
|
||||
versions = versions.stream().filter(predicate).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
list.getItems().setAll(versions);
|
||||
};
|
||||
chkRelease.selectedProperty().addListener(listener);
|
||||
chkSnapshot.selectedProperty().addListener(listener);
|
||||
chkOld.selectedProperty().addListener(listener);
|
||||
queryString.addListener(listener);
|
||||
|
||||
btnRefresh.setGraphic(wrap(SVG.REFRESH.createIcon(Theme.blackFill(), -1, -1)));
|
||||
|
||||
@@ -215,50 +283,6 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
callback.run();
|
||||
});
|
||||
|
||||
{
|
||||
toolbarPane = new TransitionPane();
|
||||
searchBar = new HBox();
|
||||
searchBar.setAlignment(Pos.CENTER);
|
||||
searchBar.setPadding(new Insets(0, 5, 0, 5));
|
||||
searchField = new JFXTextField();
|
||||
searchField.setPromptText(i18n("search"));
|
||||
HBox.setHgrow(searchField, Priority.ALWAYS);
|
||||
|
||||
JFXButton closeSearchBar = new JFXButton();
|
||||
closeSearchBar.getStyleClass().add("jfx-tool-bar-button");
|
||||
closeSearchBar.setGraphic(wrap(SVG.CLOSE.createIcon(Theme.blackFill(), -1, -1)));
|
||||
closeSearchBar.setOnAction(e -> {
|
||||
toolbarPane.setContent(checkPane, ContainerAnimations.FADE);
|
||||
isSearching = false;
|
||||
searchField.clear();
|
||||
list.getItems().setAll(loadVersions());
|
||||
});
|
||||
onEscPressed(searchField, closeSearchBar::fire);
|
||||
|
||||
searchBar.getChildren().setAll(searchField, closeSearchBar);
|
||||
|
||||
JFXButton searchButton = new JFXButton(i18n("search"));
|
||||
searchButton.getStyleClass().add("jfx-tool-bar-button");
|
||||
searchButton.setGraphic(wrap(SVG.MAGNIFY.createIcon(Theme.blackFill(), -1, -1)));
|
||||
searchButton.setOnAction(e -> {
|
||||
toolbarPane.setContent(searchBar, ContainerAnimations.FADE);
|
||||
searchField.requestFocus();
|
||||
});
|
||||
|
||||
checkPane.getChildren().add(checkPane.getChildren().size() - 1, searchButton);
|
||||
|
||||
centrePane.getContent().remove(checkPane);
|
||||
toolbarPane.setContent(checkPane, ContainerAnimations.FADE);
|
||||
centrePane.getContent().add(0, toolbarPane);
|
||||
|
||||
PauseTransition pause = new PauseTransition(Duration.millis(100));
|
||||
pause.setOnFinished(e -> search());
|
||||
searchField.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
pause.setRate(1);
|
||||
pause.playFromStart();
|
||||
});
|
||||
}
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
@@ -331,43 +355,14 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
refresh();
|
||||
}
|
||||
|
||||
private void onBack() { navigation.onPrev(true); }
|
||||
private void onBack() {
|
||||
navigation.onPrev(true);
|
||||
}
|
||||
|
||||
private void onSponsor() {
|
||||
FXUtils.openLink("https://bmclapidoc.bangbang93.com");
|
||||
}
|
||||
|
||||
private void search() {
|
||||
isSearching = true;
|
||||
String queryString = searchField.getText();
|
||||
|
||||
if (isBlank(queryString)) {
|
||||
list.getItems().setAll(loadVersions());
|
||||
} else {
|
||||
list.getItems().clear();
|
||||
|
||||
Predicate<String> predicate;
|
||||
if (queryString.startsWith("regex:")) {
|
||||
try {
|
||||
Pattern pattern = Pattern.compile(queryString.substring("regex:".length()));
|
||||
predicate = s -> pattern.matcher(s).find();
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Illegal regular expression", e);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
String lowerQueryString = queryString.toLowerCase(Locale.ROOT);
|
||||
predicate = s -> s.toLowerCase(Locale.ROOT).contains(lowerQueryString);
|
||||
}
|
||||
|
||||
for (RemoteVersion version : loadVersions()) {
|
||||
if (predicate.test(version.getSelfVersion())) {
|
||||
list.getItems().add(version);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class RemoteVersionListCell extends ListCell<RemoteVersion> {
|
||||
final IconedTwoLineListItem content = new IconedTwoLineListItem();
|
||||
final RipplerContainer ripplerContainer = new RipplerContainer(content);
|
||||
@@ -404,7 +399,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
|
||||
content.setTitle(remoteVersion.getSelfVersion());
|
||||
if (remoteVersion.getReleaseDate() != null) {
|
||||
content.setSubtitle(formatDateTime(remoteVersion.getReleaseDate()));
|
||||
content.setSubtitle(I18n.formatDateTime(remoteVersion.getReleaseDate()));
|
||||
} else {
|
||||
content.setSubtitle(null);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ import org.jackhuang.hmcl.ui.versions.VersionSettingsPage;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class LauncherSettingsPage extends DecoratorAnimatedPage implements DecoratorPage, PageAware {
|
||||
@@ -63,67 +62,25 @@ 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);
|
||||
});
|
||||
|
||||
{
|
||||
AdvancedListBox sideBar = new AdvancedListBox()
|
||||
.addNavigationDrawerItem(settingsItem -> {
|
||||
settingsItem.setTitle(i18n("settings.type.global.manage"));
|
||||
settingsItem.setLeftGraphic(wrap(SVG.GAMEPAD));
|
||||
settingsItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(gameTab));
|
||||
settingsItem.setOnAction(e -> tab.select(gameTab));
|
||||
})
|
||||
.addNavigationDrawerItem(javaItem -> {
|
||||
javaItem.setTitle(i18n("java.management"));
|
||||
javaItem.setLeftGraphic(wrap(SVG.WRENCH_OUTLINE));
|
||||
javaItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(javaManagementTab));
|
||||
javaItem.setOnAction(e -> tab.select(javaManagementTab));
|
||||
})
|
||||
.startCategory(i18n("launcher").toUpperCase(Locale.ROOT))
|
||||
.addNavigationDrawerItem(settingsItem -> {
|
||||
settingsItem.setTitle(i18n("settings.launcher.general"));
|
||||
settingsItem.setLeftGraphic(wrap(SVG.APPLICATION_OUTLINE));
|
||||
settingsItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(settingsTab));
|
||||
settingsItem.setOnAction(e -> tab.select(settingsTab));
|
||||
})
|
||||
.addNavigationDrawerItem(personalizationItem -> {
|
||||
personalizationItem.setTitle(i18n("settings.launcher.appearance"));
|
||||
personalizationItem.setLeftGraphic(wrap(SVG.STYLE_OUTLINE));
|
||||
personalizationItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(personalizationTab));
|
||||
personalizationItem.setOnAction(e -> tab.select(personalizationTab));
|
||||
})
|
||||
.addNavigationDrawerItem(downloadItem -> {
|
||||
downloadItem.setTitle(i18n("download"));
|
||||
downloadItem.setLeftGraphic(wrap(SVG.DOWNLOAD_OUTLINE));
|
||||
downloadItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(downloadTab));
|
||||
downloadItem.setOnAction(e -> tab.select(downloadTab));
|
||||
})
|
||||
.startCategory(i18n("help").toUpperCase(Locale.ROOT))
|
||||
.addNavigationDrawerItem(helpItem -> {
|
||||
helpItem.setTitle(i18n("help"));
|
||||
helpItem.setLeftGraphic(wrap(SVG.HELP_CIRCLE_OUTLINE));
|
||||
helpItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(helpTab));
|
||||
helpItem.setOnAction(e -> tab.select(helpTab));
|
||||
})
|
||||
.addNavigationDrawerItem(feedbackItem -> {
|
||||
feedbackItem.setTitle(i18n("feedback"));
|
||||
feedbackItem.setLeftGraphic(wrap(SVG.MESSAGE_ALERT_OUTLINE));
|
||||
feedbackItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(feedbackTab));
|
||||
feedbackItem.setOnAction(e -> tab.select(feedbackTab));
|
||||
})
|
||||
.addNavigationDrawerItem(aboutItem -> {
|
||||
aboutItem.setTitle(i18n("about"));
|
||||
aboutItem.setLeftGraphic(wrap(SVG.INFORMATION_OUTLINE));
|
||||
aboutItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(aboutTab));
|
||||
aboutItem.setOnAction(e -> tab.select(aboutTab));
|
||||
});
|
||||
FXUtils.setLimitWidth(sideBar, 200);
|
||||
setLeft(sideBar);
|
||||
}
|
||||
AdvancedListBox sideBar = new AdvancedListBox()
|
||||
.addNavigationDrawerTab(tab, gameTab, i18n("settings.type.global.manage"), SVG.GAMEPAD)
|
||||
.addNavigationDrawerTab(tab, javaManagementTab, i18n("java.management"), SVG.WRENCH_OUTLINE)
|
||||
.startCategory(i18n("launcher").toUpperCase(Locale.ROOT))
|
||||
.addNavigationDrawerTab(tab, settingsTab, i18n("settings.launcher.general"), SVG.APPLICATION_OUTLINE)
|
||||
.addNavigationDrawerTab(tab, personalizationTab, i18n("settings.launcher.appearance"), SVG.STYLE_OUTLINE)
|
||||
.addNavigationDrawerTab(tab, downloadTab, i18n("download"), SVG.DOWNLOAD_OUTLINE)
|
||||
.startCategory(i18n("help").toUpperCase(Locale.ROOT))
|
||||
.addNavigationDrawerTab(tab, helpTab, i18n("help"), SVG.HELP_CIRCLE_OUTLINE)
|
||||
.addNavigationDrawerTab(tab, feedbackTab, i18n("feedback"), SVG.MESSAGE_ALERT_OUTLINE)
|
||||
.addNavigationDrawerTab(tab, aboutTab, i18n("about"), SVG.INFORMATION_OUTLINE);
|
||||
FXUtils.setLimitWidth(sideBar, 200);
|
||||
setLeft(sideBar);
|
||||
|
||||
setCenter(transitionPane);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -240,7 +240,7 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
|
||||
FXUtils.unbind(txtWrapper, versionSetting.wrapperProperty());
|
||||
FXUtils.unbind(txtPreLaunchCommand, versionSetting.preLaunchCommandProperty());
|
||||
FXUtils.unbind(txtPostExitCommand, versionSetting.postExitCommandProperty());
|
||||
FXUtils.unbindEnum(cboRenderer);
|
||||
FXUtils.unbindEnum(cboRenderer, versionSetting.rendererProperty());
|
||||
noGameCheckPane.selectedProperty().unbindBidirectional(versionSetting.notCheckGameProperty());
|
||||
noJVMCheckPane.selectedProperty().unbindBidirectional(versionSetting.notCheckJVMProperty());
|
||||
noJVMArgsPane.selectedProperty().unbindBidirectional(versionSetting.noJVMArgsProperty());
|
||||
|
||||
@@ -95,11 +95,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
private Runnable retrySearch;
|
||||
|
||||
public DownloadListPage(RemoteModRepository repository) {
|
||||
this(repository, null);
|
||||
}
|
||||
|
||||
public DownloadListPage(RemoteModRepository repository, DownloadPage.DownloadCallback callback) {
|
||||
this(repository, callback, false);
|
||||
this(repository, null, false);
|
||||
}
|
||||
|
||||
public DownloadListPage(RemoteModRepository repository, DownloadPage.DownloadCallback callback, boolean versionSelection) {
|
||||
|
||||
@@ -305,7 +305,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
|
||||
ComponentList sublist = new ComponentList(() -> {
|
||||
ArrayList<ModItem> items = new ArrayList<>(versions.size());
|
||||
for (RemoteMod.Version v: versions) {
|
||||
for (RemoteMod.Version v : versions) {
|
||||
items.add(new ModItem(v, control));
|
||||
}
|
||||
return items;
|
||||
@@ -439,7 +439,9 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
|
||||
private static final class ModVersion extends JFXDialogLayout {
|
||||
public ModVersion(RemoteMod.Version version, DownloadPage selfPage) {
|
||||
this.setHeading(new HBox(new Label(i18n("mods.download.title", version.getName()))));
|
||||
boolean isModpack = selfPage.repository.getType() == RemoteModRepository.Type.MODPACK;
|
||||
|
||||
this.setHeading(new HBox(new Label(i18n(isModpack ? "modpack.download.title" : "mods.download.title", version.getName()))));
|
||||
|
||||
VBox box = new VBox(8);
|
||||
box.setPadding(new Insets(8));
|
||||
@@ -461,10 +463,10 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
|
||||
this.setBody(box);
|
||||
|
||||
JFXButton downloadButton = new JFXButton(i18n("mods.install"));
|
||||
JFXButton downloadButton = new JFXButton(isModpack ? i18n("install.modpack") : i18n("mods.install"));
|
||||
downloadButton.getStyleClass().add("dialog-accept");
|
||||
downloadButton.setOnAction(e -> {
|
||||
if (!spinnerPane.isLoading() && spinnerPane.getFailedReason() == null) {
|
||||
if (isModpack || !spinnerPane.isLoading() && spinnerPane.getFailedReason() == null) {
|
||||
fireEvent(new DialogCloseEvent());
|
||||
}
|
||||
selfPage.download(version);
|
||||
|
||||
@@ -87,26 +87,10 @@ public class GameListPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
}
|
||||
|
||||
AdvancedListBox bottomLeftCornerList = new AdvancedListBox()
|
||||
.addNavigationDrawerItem(installNewGameItem -> {
|
||||
installNewGameItem.setTitle(i18n("install.new_game"));
|
||||
installNewGameItem.setLeftGraphic(VersionPage.wrap(SVG.PLUS_CIRCLE_OUTLINE));
|
||||
installNewGameItem.setOnAction(e -> Versions.addNewGame());
|
||||
})
|
||||
.addNavigationDrawerItem(installModpackItem -> {
|
||||
installModpackItem.setTitle(i18n("install.modpack"));
|
||||
installModpackItem.setLeftGraphic(VersionPage.wrap(SVG.PACK));
|
||||
installModpackItem.setOnAction(e -> Versions.importModpack());
|
||||
})
|
||||
.addNavigationDrawerItem(refreshItem -> {
|
||||
refreshItem.setTitle(i18n("button.refresh"));
|
||||
refreshItem.setLeftGraphic(VersionPage.wrap(SVG.REFRESH));
|
||||
refreshItem.setOnAction(e -> gameList.refreshList());
|
||||
})
|
||||
.addNavigationDrawerItem(globalManageItem -> {
|
||||
globalManageItem.setTitle(i18n("settings.type.global.manage"));
|
||||
globalManageItem.setLeftGraphic(VersionPage.wrap(SVG.GEAR_OUTLINE));
|
||||
globalManageItem.setOnAction(e -> modifyGlobalGameSettings());
|
||||
});
|
||||
.addNavigationDrawerItem(i18n("install.new_game"), SVG.PLUS_CIRCLE_OUTLINE, Versions::addNewGame)
|
||||
.addNavigationDrawerItem(i18n("install.modpack"), SVG.PACK, Versions::importModpack)
|
||||
.addNavigationDrawerItem(i18n("button.refresh"), SVG.REFRESH, gameList::refreshList)
|
||||
.addNavigationDrawerItem(i18n("settings.type.global.manage"), SVG.GEAR_OUTLINE, this::modifyGlobalGameSettings);
|
||||
FXUtils.setLimitHeight(bottomLeftCornerList, 40 * 4 + 12 * 2);
|
||||
setLeft(pane, bottomLeftCornerList);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -308,25 +309,14 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
);
|
||||
|
||||
AdvancedListBox toolbar = new AdvancedListBox()
|
||||
.addNavigationDrawerItem(upgradeItem -> {
|
||||
upgradeItem.setTitle(i18n("version.update"));
|
||||
upgradeItem.setLeftGraphic(wrap(SVG.UPDATE));
|
||||
.addNavigationDrawerItem(i18n("version.update"), SVG.UPDATE, control::updateGame, upgradeItem -> {
|
||||
upgradeItem.visibleProperty().bind(control.currentVersionUpgradable);
|
||||
upgradeItem.setOnAction(e -> control.updateGame());
|
||||
})
|
||||
.addNavigationDrawerItem(testGameItem -> {
|
||||
testGameItem.setTitle(i18n("version.launch.test"));
|
||||
testGameItem.setLeftGraphic(wrap(SVG.ROCKET_LAUNCH_OUTLINE));
|
||||
testGameItem.setOnAction(e -> control.testGame());
|
||||
})
|
||||
.addNavigationDrawerItem(browseMenuItem -> {
|
||||
browseMenuItem.setTitle(i18n("settings.game.exploration"));
|
||||
browseMenuItem.setLeftGraphic(wrap(SVG.FOLDER_OUTLINE));
|
||||
.addNavigationDrawerItem(i18n("version.launch.test"), SVG.ROCKET_LAUNCH_OUTLINE, control::testGame)
|
||||
.addNavigationDrawerItem(i18n("settings.game.exploration"), SVG.FOLDER_OUTLINE, null, browseMenuItem -> {
|
||||
browseMenuItem.setOnAction(e -> browsePopup.show(browseMenuItem, JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.LEFT, browseMenuItem.getWidth(), 0));
|
||||
})
|
||||
.addNavigationDrawerItem(managementItem -> {
|
||||
managementItem.setTitle(i18n("settings.game.management"));
|
||||
managementItem.setLeftGraphic(wrap(SVG.WRENCH_OUTLINE));
|
||||
.addNavigationDrawerItem(i18n("settings.game.management"), SVG.WRENCH_OUTLINE, null, managementItem -> {
|
||||
managementItem.setOnAction(e -> managementPopup.show(managementItem, JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.LEFT, managementItem.getWidth(), 0));
|
||||
});
|
||||
toolbar.getStyleClass().add("advanced-list-box-clear-padding");
|
||||
|
||||
@@ -46,6 +46,7 @@ import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
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;
|
||||
@@ -65,6 +66,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
|
||||
private static final ObjectProperty<OperatingSystem.PhysicalMemoryStatus> memoryStatus = new SimpleObjectProperty<>(OperatingSystem.PhysicalMemoryStatus.INVALID);
|
||||
private static TimerTask memoryStatusUpdateTask;
|
||||
|
||||
private static void initMemoryStatusUpdateTask() {
|
||||
FXUtils.checkFxUserThread();
|
||||
if (memoryStatusUpdateTask != null)
|
||||
@@ -135,10 +137,6 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
rootPane.getStyleClass().add("card-list");
|
||||
|
||||
if (globalSetting) {
|
||||
HintPane skinHint = new HintPane(MessageDialogPane.MessageType.INFO);
|
||||
skinHint.setText(i18n("settings.skin"));
|
||||
rootPane.getChildren().add(skinHint);
|
||||
|
||||
HintPane specificSettingsHint = new HintPane(MessageDialogPane.MessageType.WARNING);
|
||||
Text text = new Text();
|
||||
text.textProperty().bind(BindingMapping.of(selectedVersion)
|
||||
@@ -190,6 +188,31 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
componentList = new ComponentList();
|
||||
componentList.setDepth(1);
|
||||
|
||||
if (!globalSetting) {
|
||||
BorderPane copyGlobalPane = new BorderPane();
|
||||
{
|
||||
Label label = new Label(i18n("settings.game.copy_global"));
|
||||
copyGlobalPane.setLeft(label);
|
||||
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
|
||||
|
||||
JFXButton copyAll = new JFXButton(i18n("settings.game.copy_global.copy_all"));
|
||||
copyAll.disableProperty().bind(modpack);
|
||||
copyGlobalPane.setRight(copyAll);
|
||||
copyAll.setOnAction(e -> Controllers.confirm(i18n("settings.game.copy_global.copy_all.confirm"), null, () -> {
|
||||
Set<String> ignored = new HashSet<>(Arrays.asList(
|
||||
"usesGlobal",
|
||||
"versionIcon"
|
||||
));
|
||||
|
||||
PropertyUtils.copyProperties(profile.getGlobal(), lastVersionSetting, name -> !ignored.contains(name));
|
||||
}, null));
|
||||
copyAll.getStyleClass().add("jfx-button-border");
|
||||
BorderPane.setAlignment(copyAll, Pos.CENTER_RIGHT);
|
||||
}
|
||||
|
||||
componentList.getContent().add(copyGlobalPane);
|
||||
}
|
||||
|
||||
javaItem = new MultiFileItem<>();
|
||||
javaSublist = new ComponentSublist();
|
||||
javaSublist.getContent().add(javaItem);
|
||||
@@ -446,7 +469,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
showAdvancedSettingPane.setRight(button);
|
||||
}
|
||||
|
||||
componentList.getContent().setAll(
|
||||
componentList.getContent().addAll(
|
||||
javaSublist,
|
||||
gameDirSublist,
|
||||
maxMemoryPane,
|
||||
@@ -528,11 +551,11 @@ 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);
|
||||
FXUtils.unbindEnum(cboProcessPriority);
|
||||
FXUtils.unbindEnum(cboLauncherVisibility, lastVersionSetting.launcherVisibilityProperty());
|
||||
FXUtils.unbindEnum(cboProcessPriority, lastVersionSetting.processPriorityProperty());
|
||||
|
||||
lastVersionSetting.usesGlobalProperty().removeListener(usesGlobalListener);
|
||||
lastVersionSetting.javaVersionTypeProperty().removeListener(javaListener);
|
||||
@@ -563,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;
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
import org.jackhuang.hmcl.mod.ModManager;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
@@ -60,7 +62,11 @@ public final class NativePatcher {
|
||||
});
|
||||
}
|
||||
|
||||
public static Version patchNative(Version version, String gameVersion, JavaRuntime javaVersion, VersionSetting settings) {
|
||||
public static Version patchNative(DefaultGameRepository repository,
|
||||
Version version, String gameVersion,
|
||||
JavaRuntime javaVersion,
|
||||
VersionSetting settings,
|
||||
List<String> javaArguments) {
|
||||
if (settings.getNativesDirType() == NativesDirectoryType.CUSTOM) {
|
||||
if (gameVersion != null && GameVersionNumber.compare(gameVersion, "1.19") < 0)
|
||||
return version;
|
||||
@@ -124,6 +130,7 @@ public final class NativePatcher {
|
||||
return version;
|
||||
}
|
||||
|
||||
boolean lwjglVersionChanged = false;
|
||||
ArrayList<Library> newLibraries = new ArrayList<>();
|
||||
for (Library library : version.getLibraries()) {
|
||||
if (!library.appliesToCurrentEnvironment())
|
||||
@@ -145,14 +152,33 @@ public final class NativePatcher {
|
||||
} else if (replacement != null) {
|
||||
LOG.info("Replace " + library.getName() + " with " + replacement.getName());
|
||||
newLibraries.add(replacement);
|
||||
|
||||
if ("org.lwjgl:lwjgl".equals(library.getName()) && !Objects.equals(library.getVersion(), replacement.getVersion())) {
|
||||
lwjglVersionChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lwjglVersionChanged) {
|
||||
ModManager modManager = repository.getModManager(version.getId());
|
||||
try {
|
||||
for (LocalModFile mod : modManager.getMods()) {
|
||||
if ("sodium".equals(mod.getId())) {
|
||||
// https://github.com/CaffeineMC/sodium/issues/2561
|
||||
javaArguments.add("-Dsodium.checks.issue2561=false");
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Failed to get mods", e);
|
||||
}
|
||||
}
|
||||
|
||||
return version.setLibraries(newLibraries);
|
||||
}
|
||||
|
||||
public static Library getMesaLoader(JavaRuntime javaVersion, Renderer renderer) {
|
||||
public static Library getMesaLoader(JavaRuntime javaVersion) {
|
||||
return getNatives(javaVersion.getPlatform()).get("mesa-loader");
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,7 @@ public final class I18n {
|
||||
public static String formatDateTime(Instant instant) {
|
||||
DateTimeFormatter formatter = dateTimeFormatter;
|
||||
if (formatter == null) {
|
||||
formatter = dateTimeFormatter = DateTimeFormatter.ofPattern(getResourceBundle().getString("world.time")).withZone(ZoneId.systemDefault());
|
||||
formatter = dateTimeFormatter = DateTimeFormatter.ofPattern(getResourceBundle().getString("datetime.format")).withZone(ZoneId.systemDefault());
|
||||
}
|
||||
|
||||
return formatter.format(instant);
|
||||
|
||||
@@ -37,7 +37,7 @@ public final class Locales {
|
||||
/**
|
||||
* English
|
||||
*/
|
||||
public static final SupportedLocale EN = new SupportedLocale(Locale.ENGLISH);
|
||||
public static final SupportedLocale EN = new SupportedLocale(Locale.ROOT);
|
||||
|
||||
/**
|
||||
* Spanish
|
||||
|
||||
@@ -141,18 +141,20 @@ account.move_to_global=Convert to Global Account\nThe account information will b
|
||||
account.move_to_portable=Convert to Portable Account\nThe account information will be saved in a config file in the same directory as HMCL.
|
||||
account.not_logged_in=Not Logged in
|
||||
account.password=Password
|
||||
account.portable=Portable Account
|
||||
account.portable=Portable
|
||||
account.skin=Skin
|
||||
account.skin.file=Skin File
|
||||
account.skin.model=Model
|
||||
account.skin.model.default=Classic
|
||||
account.skin.model.slim=Slim
|
||||
account.skin.type.alex=Alex
|
||||
account.skin.type.csl_api=Blessing Skin
|
||||
account.skin.type.csl_api.location=Address
|
||||
account.skin.type.csl_api.location.hint=CustomSkinAPI URL
|
||||
account.skin.type.little_skin=LittleSkin
|
||||
account.skin.type.little_skin.hint=You need to create a player with the same player name as your offline account on your skin provider site. Your skin will now be set to the skin assigned to your player on the skin provider site.
|
||||
account.skin.type.local_file=Local Skin File
|
||||
account.skin.type.steve=Steve
|
||||
account.skin.upload=Upload/Edit Skin
|
||||
account.skin.upload.failed=Failed to upload skin.
|
||||
account.skin.invalid_skin=Invalid skin file.
|
||||
@@ -319,6 +321,8 @@ curse.sort.name=Name
|
||||
curse.sort.popularity=Popularity
|
||||
curse.sort.total_downloads=Total Downloads
|
||||
|
||||
datetime.format=MMM d, yyyy, h\:mm\:ss a
|
||||
|
||||
download=Download
|
||||
download.hint=Install games and modpacks or download mods, resource packs, and worlds.
|
||||
download.code.404=File "%s" not found on the remote server.
|
||||
@@ -846,6 +850,7 @@ modpack.completion=Downloading dependencies
|
||||
modpack.desc=Describe your modpack, including an introduction and probably some changelog. Markdown and images from URL are currently supported.
|
||||
modpack.description=Modpack Description
|
||||
modpack.download=Download Modpacks
|
||||
modpack.download.title=Download Modpack - %1s
|
||||
modpack.enter_name=Enter a name for this modpack.
|
||||
modpack.export=Export as Modpack
|
||||
modpack.export.as=Export Modpack As...
|
||||
@@ -1088,7 +1093,6 @@ world.name=World Name
|
||||
world.name.enter=Enter the world name
|
||||
world.reveal=Reveal in Explorer
|
||||
world.show_all=Show All
|
||||
world.time=h\:mm\:ss a, EEE, MMM d yyyy
|
||||
|
||||
profile=Game Directories
|
||||
profile.already_exists=This name already exists. Please use a different name.
|
||||
@@ -1204,6 +1208,9 @@ settings.advanced.wrapper_launcher.prompt=Allows launching using an extra wrappe
|
||||
settings.custom=Custom
|
||||
|
||||
settings.game=Settings
|
||||
settings.game.copy_global=Copy from Global Settings
|
||||
settings.game.copy_global.copy_all=Copy All
|
||||
settings.game.copy_global.copy_all.confirm=Are you sure you want to overwrite the current instance settings? This action cannot be undone!
|
||||
settings.game.current=Game
|
||||
settings.game.dimension=Resolution
|
||||
settings.game.exploration=Explore
|
||||
@@ -1270,7 +1277,6 @@ settings.memory.lower_bound=Minimum Memory
|
||||
settings.memory.used_per_total=%1$.1f GB Used / %2$.1f GB Total
|
||||
settings.physical_memory=Physical Memory Size
|
||||
settings.show_log=Show Logs
|
||||
settings.skin=Skins for offline accounts are now supported. You can navigate to your account list to change your skin and cape, but other players cannot see it in multiplayer.
|
||||
settings.tabs.installers=Loaders
|
||||
settings.take_effect_after_restart=Applies After Restart
|
||||
settings.type=Settings Type of Instance
|
||||
|
||||
@@ -319,6 +319,8 @@ curse.sort.name=Nombre
|
||||
curse.sort.popularity=Popularidad
|
||||
curse.sort.total_downloads=Descargas totales
|
||||
|
||||
datetime.format=d MMM yyyy, H\:mm\:ss
|
||||
|
||||
download=Descargar
|
||||
download.hint=Instalar juegos y modpacks o descargar mods, paquetes de recursos y mundos.
|
||||
download.code.404=Archivo no encontrado en el servidor remoto: %s
|
||||
@@ -1035,7 +1037,6 @@ world.name=Nombre del mundo
|
||||
world.name.enter=Introducir el nombre del mundo
|
||||
world.reveal=Abrir en el Explorador
|
||||
world.show_all=Mostrar todo
|
||||
world.time=EEE, MMM d, yyyy HH\:mm\:ss
|
||||
|
||||
profile=Directorios del juego
|
||||
profile.already_exists=Este nombre ya existe, por favor, utilice un nombre diferente.
|
||||
@@ -1216,7 +1217,6 @@ settings.memory.lower_bound=Memoria mínima
|
||||
settings.memory.used_per_total=%1$.1f GB Utilizados / %2$.1f GB Totales
|
||||
settings.physical_memory=Tamaño de la memoria física
|
||||
settings.show_log=Mostrar registros
|
||||
settings.skin=Ahora se admiten los skins para las cuentas sin conexión. Puedes ir a la configuración de la cuenta para cambiar tu skin y tu capa, pero otros jugadores no podrán verlo en el multijugador.
|
||||
settings.tabs.installers=Cargadores
|
||||
settings.take_effect_after_restart=Se aplica después de reiniciar
|
||||
settings.type=Tipo de configuración de la instancia
|
||||
|
||||
@@ -283,6 +283,8 @@ curse.sort.name=名前
|
||||
curse.sort.popularity=人気
|
||||
curse.sort.total_downloads=合計ダウンロード数
|
||||
|
||||
datetime.format=yyyy/MM/dd H:mm:ss
|
||||
|
||||
download=ダウンロード
|
||||
download.hint=ゲームや modpack をインストールするか、mod、リソース パック、マップをダウンロードします
|
||||
download.code.404=リモートサーバーにファイルが見つかりません:%s
|
||||
@@ -717,7 +719,6 @@ world.name=マップ名
|
||||
world.name.enter=マップ名を入力してください
|
||||
world.reveal=フォルダを表示
|
||||
world.show_all=すべて表示
|
||||
world.time=EEE、MMM d、yyyy HH:mm:ss
|
||||
|
||||
profile=ゲームディレクトリ
|
||||
profile.already_exists=この名前はすでに存在します。別の名前を使用してください。
|
||||
@@ -873,7 +874,6 @@ settings.memory.lower_bound=最小メモリ
|
||||
settings.memory.used_per_total=%1$.1f GB使用済み/%2$.1fGB合計
|
||||
settings.physical_memory=物理メモリサイズ
|
||||
settings.show_log=ログを表示
|
||||
settings.skin=オフラインアカウントのスキンの変更がサポートされるようになりました!アカウントページに移動して、今すぐ肌を変えてください!
|
||||
settings.tabs.installers=Installers
|
||||
settings.take_effect_after_restart=再起動後に有効になります
|
||||
settings.type=バージョン設定タイプ
|
||||
|
||||
@@ -50,33 +50,35 @@ about.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL)
|
||||
|
||||
account=Аккаунты
|
||||
account.cape=Плащ
|
||||
account.character=Персонаж
|
||||
account.choose=Выберите персонажа
|
||||
account.character=Игрок
|
||||
account.choose=Выберите игрока
|
||||
account.create=Добавить аккаунт
|
||||
account.create.microsoft=Добавить аккаунт Microsoft
|
||||
account.create.offline=Добавить автономный аккаунт
|
||||
account.create.authlibInjector=Создайте аккаунт authlib-injector
|
||||
account.create.offline=Добавить аккаунт в режиме офлайн
|
||||
account.create.authlibInjector=Добавить аккаунт authlib-injector
|
||||
account.email=Почта
|
||||
account.failed=Не удалось проверить аккаунт
|
||||
account.failed.character_deleted=Персонаж уже был удалён.
|
||||
account.failed=Не удалось обновить аккаунт.
|
||||
account.failed.character_deleted=Игрок уже был удален.
|
||||
account.failed.connect_authentication_server=Не удаётся связаться с серверами авторизации, проверьте свой интернет.
|
||||
account.failed.connect_injector_server=Не удаётся подключиться к серверу авторизации. Проверьте свой интернет и убедитесь, что вы ввели правильный URL-адрес.
|
||||
account.failed.injector_download_failure=Не удаётся скачать authlib-injector. Проверьте свой интернет или попробуйте переключиться на другое зеркало скачивания.
|
||||
account.failed.invalid_credentials=Неверный пароль или ограничена скорость, повторите попытку позже.
|
||||
account.failed.invalid_password=Неверный пароль
|
||||
account.failed.invalid_token=Попробуйте выйти из аккаунта и повторно войти введя пароль
|
||||
account.failed.invalid_token=Попробуйте выйти из аккаунта и повторно войти.
|
||||
account.failed.migration=Ваш аккаунт должен быть перенесён в Microsoft. Если вы уже сделали это, вам следует войти в свой перенесенный аккаунт Microsoft.
|
||||
account.failed.no_character=Аккаунт не имеет персонажей.
|
||||
account.failed.server_disconnected=Невозможно получить доступ к серверу авторизации, не удалось проверить информацию о аккаунте. \n\
|
||||
Вы можете выбрать «Повторить проверку аккаунта», чтобы попытаться снова. \n\
|
||||
Также можно выбрать «Пропустить проверку аккаунта», чтобы продолжить запуск игры, \n\
|
||||
но информация об аккаунте может быть не актуальной. Если срок действия информации \nо аккаунте истечёт, будет невозможно войти на сервер, который требует подтверждения \nаккаунта.
|
||||
account.failed.server_disconnected=Невозможно получить доступ к серверу авторизации, не удалось проверить информацию о аккаунте.\n\
|
||||
Вы можете выбрать «Повторить проверку аккаунта», чтобы попытаться снова.\n\
|
||||
Также можно выбрать «Пропустить проверку аккаунта», чтобы продолжить запуск игры,\n\
|
||||
но информация об аккаунте может быть не актуальной. Если срок действия информации\n\
|
||||
о аккаунте истечёт, будет невозможно войти на сервер, который требует подтверждения\n\
|
||||
аккаунта.
|
||||
account.failed.server_response_malformed=Неверный ответ сервера, видимо сервер авторизации не работает.
|
||||
account.failed.ssl=При подключении к серверу произошла ошибка SSL. Пожалуйста, попробуйте обновить Java.
|
||||
account.failed.wrong_account=Вы вошли в неверный аккаунт.
|
||||
account.hmcl.hint=Необходимо нажать на «Войти» и завершить процесс в открывшейся вкладке вашего браузера.
|
||||
# avoid too long sequence.
|
||||
account.injector.add=Новый сервер авторизации
|
||||
account.injector.empty=Ничего (Вы можете нажать на кнопку с плюсом справа, чтобы добавить)
|
||||
account.injector.empty=Ничего (Вы можете нажать кнопку «плюс», чтобы добавить еще)
|
||||
account.injector.http=Предупреждение\: Этот сервер использует небезопасный протокол HTTP, и любой игрок, находящийся между вашими соединениями, сможет увидеть ваши учётные данные в открытом виде.
|
||||
account.injector.link.homepage=Домашняя страница
|
||||
account.injector.link.register=Зарегистрироваться
|
||||
@@ -85,53 +87,78 @@ account.injector.server_url=URL-адрес сервера
|
||||
account.injector.server_name=Имя сервера
|
||||
account.login=Войти
|
||||
account.login.hint=Мы не храним ваш пароль.
|
||||
account.login.refresh=Повторный вход
|
||||
account.login.refresh.microsoft.hint=Поскольку авторизация учетной записи недействительна, вам необходимо повторно добавить свою учетную запись Microsoft
|
||||
account.login.skip=Войти в режиме офлайн
|
||||
account.login.retry=Повторить снова
|
||||
account.login.refresh=Войти снова
|
||||
account.login.refresh.microsoft.hint=Вам необходимо снова войти в аккаунт Microsoft, поскольку авторизация аккаунта недействительна.
|
||||
account.logout=Выйти
|
||||
account.register=Зарегестрироваться
|
||||
account.manage=Список аккаунтов
|
||||
account.copy_uuid=Копирование UUID
|
||||
account.methods=Способ входа
|
||||
account.methods.authlib_injector=authlib-injector
|
||||
account.methods.microsoft=Аккаунт Microsoft
|
||||
account.methods.microsoft.birth=Как изменить настройки дня рождения...
|
||||
account.methods.microsoft.deauthorize=Отменить авторизацию аккаунта
|
||||
account.methods.microsoft=Microsoft
|
||||
account.methods.microsoft.birth=Как изменить настройки даты рождения...
|
||||
account.methods.microsoft.close_page=Авторизация аккаунта Microsoft завершена. Осталось выполнить некоторые шаги, которые необходимо завершить позже. Вы можете закрыть эту страницу.
|
||||
account.methods.microsoft.deauthorize=Отменить авторизацию аккаунта
|
||||
account.methods.microsoft.error.add_family=Поскольку тебе ещё нет 18 лет, взрослый должен добавить тебя в семейный доступ, чтобы ты мог играть в Minecraft.
|
||||
account.methods.microsoft.error.add_family_probably=Проверьте в настройках своего аккаунта, исполнилось ли вам уже 18 лет или нет. Если нет, необходимо добавить семейный аккаунт.
|
||||
account.methods.microsoft.error.add_family_probably=Проверьте, что возраст, указанный в настройках вашего аккаунта, составляет не менее 18 лет. Если нет и вы считаете, что это ошибка, вы можете нажать «Как изменить настройки даты рождения...», чтобы узнать, как ее изменить.
|
||||
account.methods.microsoft.error.country_unavailable=Xbox Live недоступен в вашей стране/регионе.
|
||||
account.methods.microsoft.error.missing_xbox_account=Ваш аккаунт Microsoft не подключена к Xbox. Создайте его, прежде чем продолжить.
|
||||
account.methods.microsoft.error.no_character=На аккаунте отсутствует профиль Minecraft Java.\nФайл игры не может быть создан, пожалуйста,\nнажмите на ссылку выше, чтобы создать его
|
||||
account.methods.microsoft.error.no_character=Убедитесь, что вы приобрели Minecraft: Java Edition.\n\
|
||||
Если вы уже приобрели игру, профиль может быть еще не создан.\n\
|
||||
Нажмите «Создать профиль / Редактировать имя профиля», чтобы создать его.
|
||||
account.methods.microsoft.error.unknown=Не удалось войти. Microsoft отвечает с кодом ошибки %d.
|
||||
account.methods.microsoft.error.wrong_verify_method=Пожалуйста, войдите в систему, используя свой пароль на странице входа в аккаунт Microsoft, и не используйте проверочный код для авторизации.
|
||||
account.methods.microsoft.logging_in=Авторизация...
|
||||
account.methods.microsoft.makegameidsettings=Создать архив / изменение имени профиля
|
||||
account.methods.microsoft.hint=Вам необходимо нажать кнопку «Войти» и продолжить процесс авторизации во вновь открывшемся окне браузера.\nЕсли токен, используемый для входа в аккаунт Microsoft случайно утёк, вы можете нажать «Отменить авторизацию аккаунта» ниже, чтобы отменить авторизацию.\nЕсли у вас возникнут какие-либо проблемы, вы можете нажать кнопку справки в правом верхнем углу для получения помощи.
|
||||
account.methods.microsoft.manual=После нажатия кнопки «Войти», вы должны завершить авторизацию во вновь открывшемся окне браузера. Если окно браузера не открылось, вы можете щёлкнуть здесь, чтобы скопировать ссылку и вручную открыть её в браузере.\nЕсли токен, используемый для входа в аккаунт Microsoft случайно утёк, вы можете нажать «Отменить авторизацию аккаунта» ниже, чтобы отменить авторизацию.\n問題が発生した場合は、右上隅にあるヘルプ ボタンをクリックするとヘルプが表示されます。
|
||||
account.methods.microsoft.hint=Вам необходимо нажать кнопку «Войти» и продолжить процесс авторизации во вновь открывшемся окне браузера.\n\
|
||||
\n\
|
||||
Если токен, используемый для входа в аккаунт Microsoft случайно утёк, вы можете нажать «Отменить авторизацию аккаунта» ниже, чтобы отменить авторизацию.
|
||||
account.methods.microsoft.manual=Ваш код устройства - <b>%1$s</b>. Нажмите здесь, чтобы скопировать.\n\
|
||||
\n\
|
||||
После нажатия кнопки «Войти», вы должны завершить авторизацию во вновь открывшемся окне браузера. Если окно браузера не открылось, вы можете щёлкнуть здесь, чтобы скопировать ссылку и вручную открыть её в браузере.\n\
|
||||
\n\
|
||||
Если токен, используемый для входа в аккаунт Microsoft случайно утёк, вы можете нажать «Отменить авторизацию аккаунта» ниже, чтобы отменить авторизацию.
|
||||
account.methods.microsoft.makegameidsettings=Создать / редактировать профиль
|
||||
account.methods.microsoft.profile=Профиль аккаунта...
|
||||
account.methods.microsoft.purchase=Купить Minecraft
|
||||
account.methods.forgot_password=ЗАБЫЛ ПАРОЛЬ
|
||||
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=Рекомендуется использовать только английские буквы, цифры и символы подчёркивания.
|
||||
account.methods.offline.name.invalid=Как правило, игровые имена пользователя ограничиваются английскими буквами, цифрами и символами подчёркивания и не могут превышать 16 символов.\n\
|
||||
\n\
|
||||
\ · Действительный: HuangYu, huang_Yu, Huang_Yu_123;\n\
|
||||
\ · Недействительно: Huang Yu, Huang-Yu_%%%, Huang_Yu_hello_world_hello_world.\n\
|
||||
\n\
|
||||
Если вы считаете, что на стороне сервера есть соответствующий мод или плагин, снимающий это ограничение, вы можете проигнорировать это предупреждение.
|
||||
account.methods.offline.uuid=UUID
|
||||
account.methods.offline.uuid.hint=UUID - это уникальный идентификатор игрового персонажа в Minecraft. Способ генерации UUID различается в разных лаунчерах игр. Изменение UUID на тот, который генерируется другим лаунчером гарантирует, что игровые блоки/предметы в рюкзаке вашего офлайн аккаунта останутся. Этот параметр предназначен для экспертов. Если вы не знаете что делаете, мы не советуем вам изменять этот параметр.\nЭта опция не требуется для присоединения к серверам.
|
||||
account.methods.offline.uuid.malformed=Malformed
|
||||
account.methods.offline.uuid.hint=UUID - это уникальный идентификатор игрового персонажа в Minecraft. Способ генерации UUID различается в разных лаунчерах игр. Изменение UUID на тот, который генерируется другим лаунчером гарантирует, что игровые блоки/предметы в рюкзаке вашего офлайн аккаунта останутся. Этот параметр предназначен для экспертов. Если вы не знаете что делаете, мы не советуем вам изменять этот параметр. Эта опция не требуется для присоединения к серверам.
|
||||
account.methods.offline.uuid.malformed=Недопустимый формат.
|
||||
account.methods.forgot_password=Забыли пароль
|
||||
account.missing=Нет аккаунтов
|
||||
account.missing.add=Нажмите здесь, чтобы добавить.
|
||||
account.move_to_global=Конвертировать в глобальный аккаунт\nИнформация об аккаунте будет сохранена в файле конфигурации в каталоге текущего пользователя системы.
|
||||
account.move_to_portable=Конвертировать в портативный аккаунт\nИнформация об аккаунте будет сохранена в файле конфигурации в той же папке, что и HMCL.
|
||||
account.not_logged_in=Вход не произведён
|
||||
account.password=Пароль
|
||||
account.portable=Портативный
|
||||
account.skin=Скин
|
||||
account.skin.file=Файл скина
|
||||
account.skin.model=Модель
|
||||
account.skin.model.default=Классическая
|
||||
account.skin.model.slim=Стройная
|
||||
account.skin.type.alex=Алекс
|
||||
account.skin.type.csl_api=Blessing Skin
|
||||
account.skin.type.csl_api.location=Адрес
|
||||
account.skin.type.csl_api.location.hint=URL-адрес CustomSkinAPI
|
||||
account.skin.type.little_skin=LittleSkin
|
||||
account.skin.type.little_skin.hint=Необходимо создать персонажа с тем же именем, что и на автономном аккаунте на сайте поставщика скинов. Затем ваш скин будет вашим загруженным скином на этот сайт.
|
||||
account.skin.type.little_skin.hint=Вам нужно создать игрока с тем же именем, что и у вашего офлайн-аукнта на сайте поставщика скинов. Теперь ваш скин будет установлен на скин, назначенный вашему игроку на сайте поставщика скинов.
|
||||
account.skin.type.local_file=Локальный файл скина
|
||||
account.skin.type.steve=Стив
|
||||
account.skin.upload=Загрузить скин
|
||||
account.skin.upload.failed=Не удаётся загрузить скин
|
||||
account.skin.invalid_skin=Недопустимый файл скина
|
||||
account.skin.upload.failed=Не удаётся загрузить скин.
|
||||
account.skin.invalid_skin=Недопустимый файл скина.
|
||||
account.username=Имя пользователя
|
||||
|
||||
archive.author=Автор(ы)
|
||||
@@ -295,6 +322,8 @@ curse.sort.name=Название
|
||||
curse.sort.popularity=Популярность
|
||||
curse.sort.total_downloads=Скачиваний
|
||||
|
||||
datetime.format=d MMM yyyy г., HH\:mm\:ss
|
||||
|
||||
download=Скачать
|
||||
download.hint=Установите игры и пакеты модов или загрузите моды, пакеты ресурсов и карты
|
||||
download.code.404=Файл не найден на удалённом сервере: %s
|
||||
@@ -471,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=Недопустимое имя
|
||||
@@ -597,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=Экспортировать модпак как...
|
||||
@@ -616,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=Источник
|
||||
@@ -643,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=Выберите папку для экспорта мира
|
||||
@@ -740,51 +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=Показать все
|
||||
world.time=EEE, MMM d, yyyy HH\:mm\:ss
|
||||
|
||||
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=Папки с играми
|
||||
@@ -794,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=Приоритет процесса
|
||||
@@ -866,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
|
||||
@@ -875,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=Значок
|
||||
|
||||
@@ -914,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=Шрифт
|
||||
@@ -942,16 +1064,15 @@ settings.memory.lower_bound=Мин. объём памяти
|
||||
settings.memory.used_per_total=Использовано %1$.1f ГБ / Всего %2$.1f ГБ
|
||||
settings.physical_memory=Размер физической памяти
|
||||
settings.show_log=Показать логи
|
||||
settings.skin=Теперь поддерживаются скины для автономных аккаунтов. Можно зайти в настройки аккаунта, чтобы изменить свой скин и плащ, но другие игроки не смогут увидеть их в мультиплеере.
|
||||
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. Нажмите здесь для получения подробностей.
|
||||
@@ -960,6 +1081,8 @@ sponsor.hmcl=Hello Minecraft! Launcher — лаунчер Minecraft с откр
|
||||
system.architecture=Архитектура
|
||||
system.operating_system=Операционная система
|
||||
|
||||
unofficial.hint=Вы используете неофициальную версию HMCL. Мы не можем гарантировать безопасность.
|
||||
|
||||
update=Обновление
|
||||
update.accept=Обновить
|
||||
update.changelog=Изменения
|
||||
@@ -982,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=Завершено
|
||||
|
||||
@@ -148,12 +148,14 @@ account.skin.file=外觀圖片檔案
|
||||
account.skin.model=模型
|
||||
account.skin.model.default=寬型
|
||||
account.skin.model.slim=纖細
|
||||
account.skin.type.alex=Alex
|
||||
account.skin.type.csl_api=Blessing Skin 伺服器
|
||||
account.skin.type.csl_api.location=伺服器位址
|
||||
account.skin.type.csl_api.location.hint=CustomSkinAPI 位址
|
||||
account.skin.type.little_skin=LittleSkin 皮膚站
|
||||
account.skin.type.little_skin.hint=你需要在皮膚站中加入並使用和該離線帳戶同名角色,此時離線帳戶外觀將為皮膚站上對應角色所設定的外觀。
|
||||
account.skin.type.local_file=本機外觀圖片檔案
|
||||
account.skin.type.steve=Steve
|
||||
account.skin.upload=上傳/編輯外觀
|
||||
account.skin.upload.failed=外觀上傳失敗
|
||||
account.skin.invalid_skin=無法識別的外觀檔案
|
||||
@@ -319,6 +321,8 @@ curse.sort.name=名稱
|
||||
curse.sort.popularity=熱度
|
||||
curse.sort.total_downloads=下載量
|
||||
|
||||
datetime.format=yyyy 年 MM 月 dd 日 HH:mm:ss
|
||||
|
||||
download=下載
|
||||
download.hint=安裝遊戲和模組包或下載模組、資源包和地圖
|
||||
download.code.404=遠端伺服器沒有需要下載的檔案:%s
|
||||
@@ -660,6 +664,7 @@ modpack.completion=下載模組包相關檔案
|
||||
modpack.desc=描述你要製作的模組包,比如模組包注意事項和更新紀錄,支援 Markdown (圖片請上傳至網路)。
|
||||
modpack.description=模組包描述
|
||||
modpack.download=下載模組包
|
||||
modpack.download.title=模組包下載 - %1s
|
||||
modpack.enter_name=給遊戲取個你喜歡的名稱
|
||||
modpack.export=匯出模組包
|
||||
modpack.export.as=請選取模組包類型。若你無法決定,請選取 MCBBS 類型。
|
||||
@@ -892,7 +897,6 @@ world.name=世界名稱
|
||||
world.name.enter=輸入世界名稱
|
||||
world.reveal=開啟目錄
|
||||
world.show_all=全部顯示
|
||||
world.time=yyyy 年 MM 月 dd 日 HH:mm:ss
|
||||
|
||||
profile=遊戲目錄
|
||||
profile.already_exists=該名稱已存在
|
||||
@@ -1001,6 +1005,9 @@ settings.advanced.wrapper_launcher.prompt=如填寫「optirun」後,啟動指
|
||||
settings.custom=自訂
|
||||
|
||||
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=瀏覽
|
||||
@@ -1065,7 +1072,6 @@ settings.memory.lower_bound=最低分配
|
||||
settings.memory.used_per_total=已使用 %1$.1f GB / 總記憶體 %2$.1f GB
|
||||
settings.physical_memory=實體記憶體大小
|
||||
settings.show_log=查看日誌
|
||||
settings.skin=現已支援離線帳戶更換外觀,你可以到帳戶頁面更改離線帳戶的外觀和披風 (多人遊戲下其他玩家無法看到你的外觀)。
|
||||
settings.tabs.installers=自動安裝
|
||||
settings.take_effect_after_restart=重啟後生效
|
||||
settings.type=實例遊戲設定類型
|
||||
|
||||
@@ -157,12 +157,14 @@ account.skin.file=皮肤图片文件
|
||||
account.skin.model=模型
|
||||
account.skin.model.default=宽型
|
||||
account.skin.model.slim=纤细
|
||||
account.skin.type.alex=Alex
|
||||
account.skin.type.csl_api=Blessing Skin 服务器
|
||||
account.skin.type.csl_api.location=服务器地址
|
||||
account.skin.type.csl_api.location.hint=CustomSkinAPI 地址
|
||||
account.skin.type.little_skin=LittleSkin 皮肤站
|
||||
account.skin.type.little_skin.hint=你需要在皮肤站中创建并使用和该离线账户同名的角色,此时离线账户皮肤将显示为皮肤站上对应角色所设置的皮肤。\n你可以点击右上角帮助按钮进行求助。
|
||||
account.skin.type.local_file=本地皮肤图片文件
|
||||
account.skin.type.steve=Steve
|
||||
account.skin.upload=上传/编辑皮肤
|
||||
account.skin.upload.failed=皮肤上传失败
|
||||
account.skin.invalid_skin=无法识别的皮肤文件
|
||||
@@ -328,6 +330,8 @@ curse.sort.name=名称
|
||||
curse.sort.popularity=热度
|
||||
curse.sort.total_downloads=下载量
|
||||
|
||||
datetime.format=yyyy 年 MM 月 dd 日 HH:mm:ss
|
||||
|
||||
download=下载
|
||||
download.hint=安装游戏和整合包或下载模组、资源包和地图
|
||||
download.code.404=远程服务器不包含需要下载的文件: %s\n你可以点击右上角帮助按钮进行求助。
|
||||
@@ -671,6 +675,7 @@ modpack.completion=下载整合包相关文件
|
||||
modpack.desc=描述你要制作的整合包,比如整合包注意事项和更新记录,支持 Markdown (图片请用网络链接)
|
||||
modpack.description=整合包描述
|
||||
modpack.download=下载整合包
|
||||
modpack.download.title=整合包下载 - %1s
|
||||
modpack.enter_name=给游戏起个你喜欢的名字
|
||||
modpack.export=导出整合包
|
||||
modpack.export.as=请选择整合包类型 (若无法决定,请选择“我的世界中文论坛整合包标准”)
|
||||
@@ -903,7 +908,6 @@ world.name=世界名称
|
||||
world.name.enter=输入世界名称
|
||||
world.reveal=打开文件夹
|
||||
world.show_all=显示全部
|
||||
world.time=yyyy 年 MM 月 dd 日 HH:mm:ss
|
||||
|
||||
profile=游戏文件夹
|
||||
profile.already_exists=该名称已存在
|
||||
@@ -1012,6 +1016,9 @@ settings.advanced.wrapper_launcher.prompt=如填写“optirun”后,启动命
|
||||
settings.custom=自定义
|
||||
|
||||
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=浏览
|
||||
@@ -1076,7 +1083,6 @@ settings.memory.lower_bound=最低内存分配
|
||||
settings.memory.used_per_total=设备中已使用 %1$.1f GB / 设备总内存 %2$.1f GB
|
||||
settings.physical_memory=物理内存大小
|
||||
settings.show_log=查看日志
|
||||
settings.skin=现已支持离线账户更换皮肤,你可以到账户页面更改离线账户的皮肤和披风 (多人游戏下其他玩家无法看到你的皮肤)。
|
||||
settings.tabs.installers=自动安装
|
||||
settings.take_effect_after_restart=重启后生效
|
||||
settings.type=版本游戏设置类型
|
||||
|
||||
@@ -12,7 +12,7 @@ dependencies {
|
||||
api("com.github.steveice10:opennbt:1.5")
|
||||
api("org.nanohttpd:nanohttpd:2.3.1")
|
||||
api("org.apache.commons:commons-compress:1.25.0")
|
||||
api("org.jsoup:jsoup:1.18.1")
|
||||
api("org.jsoup:jsoup:1.18.3")
|
||||
compileOnlyApi("org.jetbrains:annotations:26.0.1")
|
||||
|
||||
if (JavaVersion.current().isJava8) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -72,6 +72,7 @@ public final class BMCLAPIDownloadProvider implements DownloadProvider {
|
||||
pair("https://files.minecraftforge.net/maven", apiRoot + "/maven"),
|
||||
pair("https://maven.minecraftforge.net", apiRoot + "/maven"),
|
||||
pair("https://maven.neoforged.net/releases/net/neoforged/forge", apiRoot + "/maven/net/neoforged/forge"),
|
||||
pair("https://maven.neoforged.net/releases/net/neoforged/neoforge", apiRoot + "/maven/net/neoforged/neoforge"),
|
||||
pair("http://dl.liteloader.com/versions/versions.json", apiRoot + "/maven/com/mumfrey/liteloader/versions.json"),
|
||||
pair("http://dl.liteloader.com/versions", apiRoot + "/maven"),
|
||||
pair("https://meta.fabricmc.net", apiRoot + "/fabric-meta"),
|
||||
|
||||
@@ -216,11 +216,6 @@ public final class LibraryAnalyzer implements Iterable<LibraryAnalyzer.LibraryMa
|
||||
|
||||
@Override
|
||||
protected String patchVersion(Version gameVersion, String libraryVersion) {
|
||||
Matcher matcher = NEO_FORGE_VERSION_MATCHER.matcher(libraryVersion);
|
||||
if (matcher.find()) {
|
||||
return matcher.group("forge");
|
||||
}
|
||||
|
||||
String res = scanVersion(gameVersion);
|
||||
if (res != null) {
|
||||
return res;
|
||||
@@ -233,6 +228,11 @@ public final class LibraryAnalyzer implements Iterable<LibraryAnalyzer.LibraryMa
|
||||
}
|
||||
}
|
||||
|
||||
Matcher matcher = NEO_FORGE_VERSION_MATCHER.matcher(libraryVersion);
|
||||
if (matcher.find()) {
|
||||
return matcher.group("forge");
|
||||
}
|
||||
|
||||
return super.patchVersion(gameVersion, libraryVersion);
|
||||
}
|
||||
|
||||
@@ -248,12 +248,15 @@ public final class LibraryAnalyzer implements Iterable<LibraryAnalyzer.LibraryMa
|
||||
|
||||
for (int i = 0; i < gameArguments.size() - 1; i++) {
|
||||
Argument argument = gameArguments.get(i);
|
||||
if (argument instanceof StringArgument && "--fml.neoForgeVersion".equals(((StringArgument) argument).getArgument())) {
|
||||
Argument next = gameArguments.get(i + 1);
|
||||
if (next instanceof StringArgument) {
|
||||
return ((StringArgument) next).getArgument();
|
||||
if (argument instanceof StringArgument) {
|
||||
String argumentValue = ((StringArgument) argument).getArgument();
|
||||
if ("--fml.neoForgeVersion".equals(argumentValue) || "--fml.forgeVersion".equals(argumentValue)) {
|
||||
Argument next = gameArguments.get(i + 1);
|
||||
if (next instanceof StringArgument) {
|
||||
return ((StringArgument) next).getArgument();
|
||||
}
|
||||
return null; // Normally, there should not be two --fml.neoForgeVersion argument.
|
||||
}
|
||||
return null; // Normally, there should not be two --fml.neoForgeVersion argument.
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -50,6 +50,8 @@ import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
*/
|
||||
public class DefaultLauncher extends Launcher {
|
||||
|
||||
private final LibraryAnalyzer analyzer;
|
||||
|
||||
public DefaultLauncher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options) {
|
||||
this(repository, version, authInfo, options, null);
|
||||
}
|
||||
@@ -60,6 +62,8 @@ public class DefaultLauncher extends Launcher {
|
||||
|
||||
public DefaultLauncher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options, ProcessListener listener, boolean daemon) {
|
||||
super(repository, version, authInfo, options, listener, daemon);
|
||||
|
||||
this.analyzer = LibraryAnalyzer.analyze(version, repository.getGameVersion(version).orElse(null));
|
||||
}
|
||||
|
||||
private Command generateCommandLine(File nativeFolder) throws IOException {
|
||||
@@ -496,7 +500,7 @@ public class DefaultLauncher extends Launcher {
|
||||
break;
|
||||
case ZINK:
|
||||
env.put("MESA_LOADER_DRIVER_OVERRIDE", "zink");
|
||||
/**
|
||||
/*
|
||||
* The amdgpu DDX is missing support for modifiers, causing Zink to fail.
|
||||
* Disable DRI3 to workaround this issue.
|
||||
*
|
||||
@@ -508,7 +512,6 @@ public class DefaultLauncher extends Launcher {
|
||||
}
|
||||
}
|
||||
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version, repository.getGameVersion(version).orElse(null));
|
||||
if (analyzer.has(LibraryAnalyzer.LibraryType.FORGE)) {
|
||||
env.put("INST_FORGE", "1");
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
@@ -158,6 +159,21 @@ public final class PropertyUtils {
|
||||
});
|
||||
}
|
||||
|
||||
public static void copyProperties(Object from, Object to, Predicate<String> predicate) {
|
||||
Class<?> type = from.getClass();
|
||||
while (!type.isInstance(to))
|
||||
type = type.getSuperclass();
|
||||
|
||||
getPropertyHandleFactories(type)
|
||||
.forEach((name, factory) -> {
|
||||
if (predicate.test(name)) {
|
||||
PropertyHandle src = factory.apply(from);
|
||||
PropertyHandle target = factory.apply(to);
|
||||
target.accessor.setValue(src.accessor.getValue());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void attachListener(Object instance, InvalidationListener listener) {
|
||||
getPropertyHandleFactories(instance.getClass())
|
||||
.forEach((name, factory) -> factory.apply(instance).observable.addListener(listener));
|
||||
|
||||
@@ -85,8 +85,6 @@ public enum Architecture {
|
||||
return this == X86 || this == X86_64;
|
||||
}
|
||||
|
||||
public static final String CURRENT_ARCH_NAME;
|
||||
public static final String SYSTEM_ARCH_NAME;
|
||||
public static final Architecture CURRENT_ARCH;
|
||||
public static final Architecture SYSTEM_ARCH;
|
||||
|
||||
@@ -104,6 +102,7 @@ public enum Architecture {
|
||||
case "ia32e":
|
||||
case "em64t":
|
||||
case "x64":
|
||||
case "intel64":
|
||||
return X86_64;
|
||||
case "x8632":
|
||||
case "x86-32":
|
||||
@@ -191,8 +190,7 @@ public enum Architecture {
|
||||
}
|
||||
|
||||
static {
|
||||
CURRENT_ARCH_NAME = System.getProperty("os.arch");
|
||||
CURRENT_ARCH = parseArchName(CURRENT_ARCH_NAME);
|
||||
CURRENT_ARCH = parseArchName(System.getProperty("os.arch"));
|
||||
|
||||
String sysArchName = null;
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
@@ -218,12 +216,6 @@ public enum Architecture {
|
||||
}
|
||||
|
||||
Architecture sysArch = parseArchName(sysArchName);
|
||||
if (sysArch == UNKNOWN) {
|
||||
SYSTEM_ARCH_NAME = CURRENT_ARCH_NAME;
|
||||
SYSTEM_ARCH = CURRENT_ARCH;
|
||||
} else {
|
||||
SYSTEM_ARCH_NAME = sysArchName;
|
||||
SYSTEM_ARCH = sysArch;
|
||||
}
|
||||
SYSTEM_ARCH = sysArch == UNKNOWN ? CURRENT_ARCH : sysArch;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user