Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b52c46664 | ||
|
|
aa8c386cdd | ||
|
|
bafe17107c | ||
|
|
9c169fb560 | ||
|
|
61ab8a6769 | ||
|
|
b1cd7057db | ||
|
|
d426952d47 | ||
|
|
67b6a60382 | ||
|
|
9d5da5a39d | ||
|
|
efb14572d6 |
No files matched your search
@@ -25,8 +25,12 @@ NVIDIA
|
||||
/HMCLCore/out/
|
||||
|
||||
# eclipse
|
||||
/bin/
|
||||
/HMCL/bin/
|
||||
/HMCLCore/bin/
|
||||
.classpath
|
||||
.project
|
||||
.settings
|
||||
|
||||
# netbeans
|
||||
.nb-gradle
|
||||
@@ -17,14 +17,16 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyListProperty;
|
||||
import javafx.beans.property.ReadOnlyListWrapper;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashSet;
|
||||
@@ -41,6 +43,8 @@ public final class Profiles {
|
||||
public static final String DEFAULT_PROFILE = "Default";
|
||||
public static final String HOME_PROFILE = "Home";
|
||||
|
||||
private static InvalidationListener listener = o -> loadVersion();
|
||||
|
||||
private Profiles() {
|
||||
}
|
||||
|
||||
@@ -61,11 +65,17 @@ public final class Profiles {
|
||||
private static ObjectProperty<Profile> selectedProfile = new SimpleObjectProperty<Profile>() {
|
||||
{
|
||||
profiles.addListener(onInvalidating(this::invalidated));
|
||||
|
||||
this.addListener(this::change);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
Profile profile = get();
|
||||
|
||||
if (get() != null)
|
||||
get().removeListener(listener);
|
||||
|
||||
if (profiles.isEmpty()) {
|
||||
if (profile != null) {
|
||||
set(null);
|
||||
@@ -80,7 +90,16 @@ public final class Profiles {
|
||||
|
||||
if (!initialized)
|
||||
return;
|
||||
|
||||
config().setSelectedProfile(profile == null ? "" : profile.getName());
|
||||
loadVersion();
|
||||
}
|
||||
|
||||
private void change(ObservableValue<? extends Profile> observableValue, Profile oldProfile, Profile newProfile) {
|
||||
if (oldProfile != null)
|
||||
oldProfile.selectedVersionProperty().removeListener(listener);
|
||||
if (newProfile != null)
|
||||
newProfile.selectedVersionProperty().addListener(listener);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -127,18 +146,29 @@ public final class Profiles {
|
||||
HashSet<String> names = new HashSet<>();
|
||||
config().getConfigurations().forEach((name, profile) -> {
|
||||
if (!names.add(name)) return;
|
||||
profile.setName(name);
|
||||
profiles.add(profile);
|
||||
profile.setName(name);
|
||||
});
|
||||
checkProfiles();
|
||||
|
||||
initialized = true;
|
||||
// Platform.runLater is necessary or profiles will be empty
|
||||
// since checkProfiles adds 2 base profile later.
|
||||
Platform.runLater(() -> {
|
||||
selectedProfile.set(
|
||||
profiles.stream()
|
||||
.filter(it -> it.getName().equals(config().getSelectedProfile()))
|
||||
.findFirst()
|
||||
.orElse(profiles.get(0)));
|
||||
|
||||
selectedProfile.set(
|
||||
profiles.stream()
|
||||
.filter(it -> it.getName().equals(config().getSelectedProfile()))
|
||||
.findFirst()
|
||||
.orElse(profiles.get(0)));
|
||||
initialized = true;
|
||||
});
|
||||
|
||||
EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> {
|
||||
JFXUtilities.runInFX(() -> {
|
||||
if (selectedProfile.get() != null && selectedProfile.get().getRepository() == event.getSource())
|
||||
loadVersion();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public static ObservableList<Profile> getProfiles() {
|
||||
@@ -160,4 +190,21 @@ public final class Profiles {
|
||||
public static ObjectProperty<Profile> selectedProfileProperty() {
|
||||
return selectedProfile;
|
||||
}
|
||||
|
||||
private static final ReadOnlyStringWrapper selectedVersion = new ReadOnlyStringWrapper();
|
||||
|
||||
public static ReadOnlyStringProperty selectedVersionProperty() {
|
||||
return selectedVersion.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public static String getSelectedVersion() {
|
||||
return selectedVersion.get();
|
||||
}
|
||||
|
||||
private static void loadVersion() {
|
||||
Profile profile = selectedProfile.get();
|
||||
if (profile == null || !profile.getRepository().isLoaded()) return;
|
||||
JFXUtilities.runInFX(() ->
|
||||
selectedVersion.set(profile.getSelectedVersion()));
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,7 @@ import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
@@ -92,6 +93,11 @@ public final class FXUtils {
|
||||
consumer.accept(value.getValue());
|
||||
}
|
||||
|
||||
public static void runLaterIf(BooleanSupplier condition, Runnable runnable) {
|
||||
if (condition.getAsBoolean()) Platform.runLater(() -> runLaterIf(condition, runnable));
|
||||
else runnable.run();
|
||||
}
|
||||
|
||||
public static void limitSize(ImageView imageView, double maxWidth, double maxHeight) {
|
||||
imageView.setPreserveRatio(true);
|
||||
onChangeAndOperate(imageView.imageProperty(), image -> {
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.mod.UnsupportedModpackException;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
@@ -86,7 +87,9 @@ public final class LeftPaneController extends AdvancedListBox {
|
||||
.add(launcherSettingsItem);
|
||||
|
||||
EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).register(event -> onRefreshedVersions((HMCLGameRepository) event.getSource()));
|
||||
if (Profiles.selectedProfileProperty().get().getRepository().isLoaded())
|
||||
|
||||
Profile profile = Profiles.getSelectedProfile();
|
||||
if (profile != null && profile.getRepository().isLoaded())
|
||||
onRefreshedVersions(Profiles.selectedProfileProperty().get().getRepository());
|
||||
}
|
||||
|
||||
|
||||
@@ -17,25 +17,55 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXPopup;
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
import org.jackhuang.hmcl.event.RefreshingVersionsEvent;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.construct.IconedMenuItem;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.ui.versions.Versions;
|
||||
import org.jackhuang.hmcl.util.VersionNumber;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class MainPage extends StackPane implements DecoratorPage {
|
||||
|
||||
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title", i18n("main_page"));
|
||||
|
||||
private final VBox menu = new VBox();
|
||||
private final JFXPopup popup = new JFXPopup(menu);
|
||||
|
||||
@FXML
|
||||
private StackPane main;
|
||||
@FXML
|
||||
private JFXButton btnLaunch;
|
||||
@FXML
|
||||
private JFXButton btnMenu;
|
||||
@FXML
|
||||
private Label lblCurrentGame;
|
||||
@FXML
|
||||
private Rectangle separator;
|
||||
|
||||
private Profile profile;
|
||||
{
|
||||
FXUtils.loadFXML(this, "/assets/fxml/main.fxml");
|
||||
|
||||
@@ -45,6 +75,54 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
else
|
||||
getChildren().setAll(main);
|
||||
});
|
||||
|
||||
btnLaunch.setClip(new Rectangle(-100, -100, 280, 200));
|
||||
btnMenu.setClip(new Rectangle(181, -100, 100, 200));
|
||||
menu.setMinWidth(200);
|
||||
|
||||
StackPane graphic = new StackPane();
|
||||
Node svg = SVG.triangle(Theme.whiteFillBinding(), 10, 10);
|
||||
StackPane.setAlignment(svg, Pos.CENTER_RIGHT);
|
||||
graphic.getChildren().setAll(svg);
|
||||
graphic.setTranslateX(12);
|
||||
btnMenu.setGraphic(graphic);
|
||||
|
||||
FXUtils.onChangeAndOperate(Profiles.selectedVersionProperty(), version -> {
|
||||
if (version != null) {
|
||||
lblCurrentGame.setText(version);
|
||||
} else {
|
||||
lblCurrentGame.setText(i18n("version.empty"));
|
||||
}
|
||||
});
|
||||
|
||||
EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).register(event -> {
|
||||
if (event.getSource() == profile.getRepository())
|
||||
loadVersions((HMCLGameRepository) event.getSource());
|
||||
});
|
||||
Profiles.selectedProfileProperty().addListener((a, b, newValue) -> profile = newValue);
|
||||
|
||||
profile = Profiles.getSelectedProfile();
|
||||
if (profile != null) {
|
||||
if (profile.getRepository().isLoaded())
|
||||
loadVersions(profile.getRepository());
|
||||
else
|
||||
profile.getRepository().refreshVersionsAsync().start();
|
||||
}
|
||||
}
|
||||
|
||||
private void loadVersions(HMCLGameRepository repository) {
|
||||
List<IconedMenuItem> children = repository.getVersions().parallelStream()
|
||||
.filter(version -> !version.isHidden())
|
||||
.sorted((a, b) -> VersionNumber.COMPARATOR.compare(VersionNumber.asVersion(a.getId()), VersionNumber.asVersion(b.getId())))
|
||||
.map(version -> new IconedMenuItem(null, version.getId(), () -> {
|
||||
repository.getProfile().setSelectedVersion(version.getId());
|
||||
popup.hide();
|
||||
}))
|
||||
.collect(Collectors.toList());
|
||||
JFXUtilities.runInFX(() -> {
|
||||
if (profile == repository.getProfile())
|
||||
menu.getChildren().setAll(children);
|
||||
});
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -53,6 +131,11 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
Versions.launch(profile, profile.getSelectedVersion());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onMenu() {
|
||||
popup.show(btnMenu, JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.RIGHT, 0, -btnMenu.getHeight());
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
}
|
||||
|
||||
@@ -150,4 +150,8 @@ public final class SVG {
|
||||
public static Node openInNew(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node triangle(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M1,21H23L12,2", fill, width, height);
|
||||
}
|
||||
}
|
||||
@@ -39,12 +39,17 @@ public class IconedItem extends RipplerContainer {
|
||||
|
||||
private static HBox createHBox(Node icon) {
|
||||
HBox hBox = new HBox();
|
||||
|
||||
if (icon != null) {
|
||||
icon.setMouseTransparent(true);
|
||||
hBox.getChildren().add(icon);
|
||||
}
|
||||
|
||||
hBox.getStyleClass().setAll("iconed-item-container");
|
||||
icon.setMouseTransparent(true);
|
||||
Label textLabel = new Label();
|
||||
textLabel.setId("label");
|
||||
textLabel.setMouseTransparent(true);
|
||||
hBox.getChildren().addAll(icon, textLabel);
|
||||
hBox.getChildren().addAll(textLabel);
|
||||
return hBox;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import javafx.animation.Interpolator;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Insets;
|
||||
@@ -93,6 +94,7 @@ public class DecoratorController {
|
||||
welcomeView = new ImageView();
|
||||
welcomeView.setImage(new Image("/assets/img/welcome.png"));
|
||||
welcomeView.setCursor(Cursor.HAND);
|
||||
FXUtils.limitSize(welcomeView, 796, 517);
|
||||
welcomeView.setOnMouseClicked(e -> {
|
||||
Timeline nowAnimation = new Timeline();
|
||||
nowAnimation.getKeyFrames().addAll(
|
||||
@@ -277,12 +279,17 @@ public class DecoratorController {
|
||||
FXUtils.checkFxUserThread();
|
||||
|
||||
if (dialog == null) {
|
||||
if (decorator.getDrawerWrapper() == null) {
|
||||
// Sometimes showDialog will be invoked before decorator was initialized.
|
||||
// Keep trying again.
|
||||
Platform.runLater(() -> showDialog(node));
|
||||
return;
|
||||
}
|
||||
|
||||
dialog = new JFXDialog();
|
||||
dialogPane = new StackContainerPane();
|
||||
|
||||
dialog.setContent(dialogPane);
|
||||
if (decorator.getDrawerWrapper() == null)
|
||||
throw new IllegalStateException("Decorator has not been initialized");
|
||||
dialog.setDialogContainer(decorator.getDrawerWrapper());
|
||||
dialog.setOverlayClose(false);
|
||||
dialog.show();
|
||||
|
||||
@@ -233,6 +233,7 @@ public class DecoratorSkin extends SkinBase<DecoratorControl> {
|
||||
Rectangle separator = new Rectangle();
|
||||
separator.visibleProperty().bind(refreshNavButton.visibleProperty());
|
||||
separator.heightProperty().bind(navBar.heightProperty());
|
||||
separator.setFill(Color.GRAY);
|
||||
|
||||
JFXButton btnMin = new JFXButton();
|
||||
StackPane pane = new StackPane(minus);
|
||||
|
||||
@@ -38,7 +38,7 @@ public class DatapackListPage extends ListPage<DatapackListItem> implements Deco
|
||||
|
||||
list = MappedObservableList.create(datapack.getInfo(), pack -> new DatapackListItem(pack, item -> {
|
||||
try {
|
||||
datapack.deletePack(pack.getId());
|
||||
datapack.deletePack(pack);
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.warning("Failed to delete datapack");
|
||||
}
|
||||
@@ -84,7 +84,7 @@ public class DatapackListPage extends ListPage<DatapackListItem> implements Deco
|
||||
public void add() {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle(i18n("datapack.choose_datapack"));
|
||||
chooser.getExtensionFilters().setAll(new FileChooser.ExtensionFilter(i18n("extension.datapack"), "*.zip"));
|
||||
chooser.getExtensionFilters().setAll(new FileChooser.ExtensionFilter(i18n("datapack.extension"), "*.zip"));
|
||||
List<File> res = chooser.showOpenMultipleDialog(Controllers.getStage());
|
||||
|
||||
if (res != null)
|
||||
|
||||
@@ -24,60 +24,34 @@ import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class GameAdvancedListItem extends AdvancedListItem {
|
||||
private final WeakListenerHolder listenerHolder = new WeakListenerHolder();
|
||||
|
||||
private Profile profile;
|
||||
private InvalidationListener listener = o -> loadVersion();
|
||||
|
||||
public GameAdvancedListItem() {
|
||||
Profiles.selectedProfileProperty().addListener(listenerHolder.weak((a, b, newValue) -> {
|
||||
JFXUtilities.runInFX(() -> loadProfile(newValue));
|
||||
}));
|
||||
listenerHolder.add(EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> {
|
||||
JFXUtilities.runInFX(() -> {
|
||||
if (profile != null && profile.getRepository() == event.getSource())
|
||||
loadVersion();
|
||||
FXUtils.onChangeAndOperate(Profiles.selectedVersionProperty(), version -> {
|
||||
FXUtils.runLaterIf(() -> !Objects.nonNull(Profiles.getSelectedProfile()), () -> {
|
||||
File iconFile = Profiles.getSelectedProfile().getRepository().getVersionIcon(version);
|
||||
if (iconFile.exists())
|
||||
imageProperty().set(new Image("file:" + iconFile.getAbsolutePath()));
|
||||
else
|
||||
imageProperty().set(new Image("/assets/img/grass.png"));
|
||||
|
||||
if (version != null) {
|
||||
titleProperty().set(version);
|
||||
subtitleProperty().set(null);
|
||||
} else {
|
||||
titleProperty().set(i18n("version.empty"));
|
||||
subtitleProperty().set(i18n("version.empty.add"));
|
||||
}
|
||||
});
|
||||
}));
|
||||
loadProfile(Profiles.getSelectedProfile());
|
||||
}
|
||||
|
||||
private void loadProfile(Profile newProfile) {
|
||||
if (profile != null)
|
||||
profile.selectedVersionProperty().removeListener(listener);
|
||||
profile = newProfile;
|
||||
if (profile != null)
|
||||
profile.selectedVersionProperty().addListener(listener);
|
||||
loadVersion();
|
||||
}
|
||||
|
||||
private void loadVersion() {
|
||||
Profile profile = this.profile;
|
||||
if (profile == null || !profile.getRepository().isLoaded()) return;
|
||||
String version = profile.getSelectedVersion();
|
||||
File iconFile = profile.getRepository().getVersionIcon(version);
|
||||
|
||||
JFXUtilities.runInFX(() -> {
|
||||
if (iconFile.exists())
|
||||
imageProperty().set(new Image("file:" + iconFile.getAbsolutePath()));
|
||||
else
|
||||
imageProperty().set(new Image("/assets/img/grass.png"));
|
||||
|
||||
if (version != null) {
|
||||
titleProperty().set(version);
|
||||
subtitleProperty().set(null);
|
||||
} else {
|
||||
titleProperty().set(i18n("version.empty"));
|
||||
subtitleProperty().set(i18n("version.empty.add"));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@ import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.game.World;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.wizard.SinglePageWizardProvider;
|
||||
import org.jackhuang.hmcl.util.IntVersionNumber;
|
||||
import org.jackhuang.hmcl.util.VersionNumber;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Date;
|
||||
@@ -24,7 +26,7 @@ public class WorldListItem extends Control {
|
||||
this.world = world;
|
||||
|
||||
title.set(world.getWorldName());
|
||||
subtitle.set(i18n("world.description", world.getFileName(), new Date(world.getLastPlayed()).toString(), world.getGameVersion()));
|
||||
subtitle.set(i18n("world.description", world.getFileName(), new Date(world.getLastPlayed()).toString(), world.getGameVersion() == null ? i18n("message.unknown") : world.getGameVersion()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -58,6 +60,12 @@ public class WorldListItem extends Control {
|
||||
}
|
||||
|
||||
public void manageDatapacks() {
|
||||
if (world.getGameVersion() == null || // old game will not write game version to level.dat
|
||||
(IntVersionNumber.isIntVersionNumber(world.getGameVersion()) // we don't parse snapshot version
|
||||
&& VersionNumber.asVersion(world.getGameVersion()).compareTo(VersionNumber.asVersion("1.13")) < 0)) {
|
||||
Controllers.dialog(i18n("world.datapack.1_13"));
|
||||
return;
|
||||
}
|
||||
Controllers.navigate(new DatapackListPage(world.getWorldName(), world.getFile()));
|
||||
}
|
||||
}
|
||||
@@ -3,16 +3,20 @@ package org.jackhuang.hmcl.ui.versions;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.game.World;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.ListPage;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
@@ -25,33 +29,38 @@ public class WorldListPage extends ListPage<WorldListItem> {
|
||||
public void loadVersion(Profile profile, String id) {
|
||||
this.savesDir = profile.getRepository().getRunDirectory(id).toPath().resolve("saves");
|
||||
|
||||
itemsProperty().clear();
|
||||
try {
|
||||
if (Files.exists(savesDir))
|
||||
for (Path worldDir : Files.newDirectoryStream(savesDir)) {
|
||||
itemsProperty().add(new WorldListItem(new World(worldDir)));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Failed to read saves", e);
|
||||
}
|
||||
itemsProperty().setAll(World.getWorlds(savesDir).stream()
|
||||
.map(WorldListItem::new).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add() {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle(i18n("world.choose_world"));
|
||||
chooser.setTitle(i18n("world.import.choose"));
|
||||
chooser.getExtensionFilters().setAll(new FileChooser.ExtensionFilter(i18n("world.extension"), "*.zip"));
|
||||
List<File> res = chooser.showOpenMultipleDialog(Controllers.getStage());
|
||||
|
||||
if (res == null) return;
|
||||
res.forEach(it -> {
|
||||
try {
|
||||
World world = new World(it.toPath());
|
||||
world.install(savesDir, world.getWorldName());
|
||||
itemsProperty().add(new WorldListItem(new World(savesDir.resolve(world.getWorldName()))));
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to parse datapack file " + it, e);
|
||||
}
|
||||
});
|
||||
if (res == null || res.isEmpty()) return;
|
||||
try {
|
||||
// Only accept one world file because user is required to confirm the new world name
|
||||
// Or too many input dialogs are popped.
|
||||
World world = new World(res.get(0).toPath());
|
||||
|
||||
Controllers.inputDialog(i18n("world.name.enter"), (name, resolve, reject) -> {
|
||||
Task.of(() -> world.install(savesDir, name))
|
||||
.finalized(Schedulers.javafx(), var -> {
|
||||
itemsProperty().add(new WorldListItem(new World(savesDir.resolve(name))));
|
||||
resolve.run();
|
||||
}, e -> {
|
||||
if (e instanceof FileAlreadyExistsException)
|
||||
reject.accept(i18n("world.import.failed", i18n("world.import.already_exists")));
|
||||
else
|
||||
reject.accept(i18n("world.import.failed", e.getClass().getName() + ": " + e.getLocalizedMessage()));
|
||||
}).start();
|
||||
}).setInitialText(world.getWorldName());
|
||||
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to parse datapack file " + res.get(0), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -510,6 +510,15 @@
|
||||
-fx-font-size:14px;
|
||||
}
|
||||
|
||||
.jfx-button-raised .jfx-rippler {
|
||||
-jfx-rippler-fill: white;
|
||||
}
|
||||
|
||||
.jfx-button-raised .label {
|
||||
-fx-text-fill: white;
|
||||
-fx-font-size: 14px;
|
||||
}
|
||||
|
||||
.jfx-button-raised-round {
|
||||
-fx-background-color: -fx-base-color;
|
||||
-fx-background-radius: 50px;
|
||||
|
||||
@@ -2,11 +2,25 @@
|
||||
|
||||
<?import com.jfoenix.controls.JFXButton?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.shape.Rectangle?>
|
||||
<fx:root type="StackPane" pickOnBounds="false"
|
||||
xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<StackPane fx:id="main" style="-fx-padding: 25;">
|
||||
<JFXButton prefWidth="150" prefHeight="50" buttonType="RAISED" styleClass="jfx-button-raised"
|
||||
style="-fx-font-size: 15;" onMouseClicked="#launch"
|
||||
text="%version.launch" StackPane.alignment="BOTTOM_RIGHT"/>
|
||||
<StackPane maxWidth="200" maxHeight="50" StackPane.alignment="BOTTOM_RIGHT">
|
||||
<JFXButton prefWidth="200" prefHeight="50" buttonType="RAISED" styleClass="jfx-button-raised"
|
||||
onMouseClicked="#launch" fx:id="btnLaunch">
|
||||
<graphic>
|
||||
<VBox alignment="CENTER" translateX="-7" maxWidth="160">
|
||||
<Label style="-fx-font-size: 15;" text="%version.launch"/>
|
||||
<Label style="-fx-font-size: 10px;" fx:id="lblCurrentGame"/>
|
||||
</VBox>
|
||||
</graphic>
|
||||
</JFXButton>
|
||||
<Rectangle fx:id="separator" translateX="80" height="52" fill="gray" width="1" mouseTransparent="true"/>
|
||||
<JFXButton prefWidth="200" prefHeight="50" buttonType="RAISED" styleClass="jfx-button-raised"
|
||||
style="-fx-font-size: 15;" onMouseClicked="#onMenu" fx:id="btnMenu" />
|
||||
</StackPane>
|
||||
</StackPane>
|
||||
</fx:root>
|
||||
@@ -247,12 +247,13 @@ mods.remove=Remove
|
||||
datapack=Data packs
|
||||
datapack.add=Add data pack
|
||||
datapack.choose_datapack=Choose the datapack zip to be imported
|
||||
datapack.extension=Datapack
|
||||
datapack.title=World %s - Datapacks
|
||||
datapack.remove=Remove
|
||||
|
||||
world=Worlds/Datapacks
|
||||
world.choose_world=Choose the save zip to be imported
|
||||
world.datapack=Manage data packs
|
||||
world.datapack.1_13=Only Minecraft 1.13 and later versions support data packs.
|
||||
world.description=%s. Last played time: %s. Game version: %s.
|
||||
world.export=Export this world
|
||||
world.export.title=Choose a file location to hold your world
|
||||
@@ -260,7 +261,11 @@ world.export.location=Export to
|
||||
world.export.wizard=Export world %s
|
||||
world.extension=World zip
|
||||
world.game_version=Game Version
|
||||
world.import.already_exists=This world already exists.
|
||||
world.import.choose=Choose the save zip to be imported
|
||||
world.import.failed=Unable to import this world: %s
|
||||
world.name=World Name
|
||||
world.name.enter=Enter the world name
|
||||
|
||||
profile=Game Directories
|
||||
profile.default=Current directory
|
||||
|
||||
@@ -244,23 +244,28 @@ mods.add.success=成功新增模組 %s。
|
||||
mods.choose_mod=選擇模組
|
||||
mods.remove=刪除
|
||||
|
||||
datapack=數據檔
|
||||
datapack.add=添加數據檔
|
||||
datapack.choose_datapack=選擇要導入的數據包壓縮檔
|
||||
datapack.title=世界 %s - 數據檔
|
||||
datapack=資料包
|
||||
datapack.add=添加資料包
|
||||
datapack.choose_datapack=選擇要導入的資料包壓縮檔
|
||||
datapack.extension=資料包
|
||||
datapack.title=世界 %s - 資料包
|
||||
datapack.remove=刪除
|
||||
|
||||
world=世界/數據檔
|
||||
world.choose_world=選擇要導入的存檔壓縮檔
|
||||
world.datapack=管理數據檔
|
||||
world=世界/資料包
|
||||
world.datapack=管理資料包
|
||||
world.datapack.1_13=僅 Minecraft 1.13 及之後的版本支持資料包
|
||||
world.description=%s. 上一次遊戲時間: %s. 遊戲版本: %s
|
||||
world.export=導出此世界
|
||||
world.export.title=選擇該世界的存儲位置
|
||||
world.export.location=保存到
|
||||
world.export.wizard=導出世界 %s
|
||||
world.extension=存檔壓縮檔
|
||||
world.import.already_exists=此世界已經存在
|
||||
world.import.choose=選擇要導入的存檔壓縮檔
|
||||
world.import.failed=無法導入此世界: %s
|
||||
world.game_version=遊戲版本
|
||||
world.name=世界名稱
|
||||
world.name.enter=輸入世界名稱
|
||||
|
||||
profile=遊戲目錄
|
||||
profile.default=目前目錄
|
||||
|
||||
@@ -247,12 +247,13 @@ mods.remove=删除
|
||||
datapack=数据包
|
||||
datapack.add=添加数据包
|
||||
datapack.choose_datapack=选择要导入的数据包压缩包
|
||||
datapack.extension=数据包
|
||||
datapack.title=世界 %s - 数据包
|
||||
datapack.remove=删除
|
||||
|
||||
world=世界/数据包
|
||||
world.choose_world=选择要导入的存档压缩包
|
||||
world.datapack=管理数据包
|
||||
world.datapack.1_13=仅 Minecraft 1.13 及之后的版本支持数据包
|
||||
world.description=%s. 上一次游戏时间: %s. 游戏版本: %s
|
||||
world.export=导出此世界
|
||||
world.export.title=选择该世界的存储位置
|
||||
@@ -260,7 +261,11 @@ world.export.location=保存到
|
||||
world.export.wizard=导出世界 %s
|
||||
world.extension=世界压缩包
|
||||
world.game_version=游戏版本
|
||||
world.import.already_exists=此世界已经存在
|
||||
world.import.choose=选择要导入的存档压缩包
|
||||
world.import.failed=无法导入此世界:%s
|
||||
world.name=世界名称
|
||||
world.name.enter=输入世界名称
|
||||
|
||||
profile=游戏目录
|
||||
profile.default=当前目录
|
||||
|
||||
@@ -140,6 +140,8 @@ public class DefaultGameRepository implements GameRepository {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fromVersion.getId().equals(fromVersion.getJar()))
|
||||
fromVersion = fromVersion.setJar(to);
|
||||
FileUtils.writeText(toJson, Constants.GSON.toJson(fromVersion.setId(to)));
|
||||
return true;
|
||||
} catch (IOException | JsonParseException | VersionNotFoundException e) {
|
||||
|
||||
@@ -5,18 +5,18 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.LongTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.StringTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
import org.jackhuang.hmcl.util.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.Unzipper;
|
||||
import org.jackhuang.hmcl.util.Zipper;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
@@ -27,7 +27,7 @@ public class World {
|
||||
private String fileName;
|
||||
private String worldName;
|
||||
private String gameVersion;
|
||||
private long lastPlayed, seed;
|
||||
private long lastPlayed;
|
||||
|
||||
public World(Path file) throws IOException {
|
||||
this.file = file;
|
||||
@@ -62,10 +62,6 @@ public class World {
|
||||
return lastPlayed;
|
||||
}
|
||||
|
||||
public long getSeed() {
|
||||
return seed;
|
||||
}
|
||||
|
||||
public String getGameVersion() {
|
||||
return gameVersion;
|
||||
}
|
||||
@@ -88,12 +84,23 @@ public class World {
|
||||
CompoundTag nbt = parseLevelDat(levelDat);
|
||||
|
||||
CompoundTag data = nbt.get("Data");
|
||||
String name = data.<StringTag>get("LevelName").getValue();
|
||||
lastPlayed = data.<LongTag>get("LastPlayed").getValue();
|
||||
seed = data.<LongTag>get("RandomSeed").getValue();
|
||||
CompoundTag version = data.get("Version");
|
||||
gameVersion = version.<StringTag>get("Name").getValue();
|
||||
worldName = name;
|
||||
if (data.get("LevelName") instanceof StringTag)
|
||||
worldName = data.<StringTag>get("LevelName").getValue();
|
||||
else
|
||||
throw new IOException("level.dat missing LevelName");
|
||||
|
||||
if (data.get("LastPlayed") instanceof LongTag)
|
||||
lastPlayed = data.<LongTag>get("LastPlayed").getValue();
|
||||
else
|
||||
throw new IOException("level.dat missing LastPlayed");
|
||||
|
||||
gameVersion = null;
|
||||
if (data.get("Version") instanceof CompoundTag) {
|
||||
CompoundTag version = data.get("Version");
|
||||
|
||||
if (version.get("Name") instanceof StringTag)
|
||||
gameVersion = version.<StringTag>get("Name").getValue();
|
||||
}
|
||||
}
|
||||
|
||||
public void rename(String newName) throws IOException {
|
||||
@@ -106,7 +113,9 @@ public class World {
|
||||
CompoundTag data = nbt.get("Data");
|
||||
data.put(new StringTag("LevelName", newName));
|
||||
|
||||
NBTIO.writeTag(new GZIPOutputStream(Files.newOutputStream(levelDat)), nbt);
|
||||
try (OutputStream os = new GZIPOutputStream(Files.newOutputStream(levelDat))) {
|
||||
NBTIO.writeTag(os, nbt);
|
||||
}
|
||||
|
||||
// then change the folder's name
|
||||
Files.move(file, file.resolveSibling(newName));
|
||||
@@ -127,9 +136,10 @@ public class World {
|
||||
}
|
||||
subDirectoryName = FileUtils.getName(subDirs.get(0));
|
||||
}
|
||||
new Unzipper(file, savesDir)
|
||||
new Unzipper(file, worldDir)
|
||||
.setSubDirectory("/" + subDirectoryName + "/")
|
||||
.unzip();
|
||||
new World(worldDir).rename(name);
|
||||
} else if (Files.isDirectory(file)) {
|
||||
FileUtils.copyDirectory(file, worldDir);
|
||||
}
|
||||
@@ -145,17 +155,28 @@ public class World {
|
||||
}
|
||||
|
||||
private static CompoundTag parseLevelDat(Path path) throws IOException {
|
||||
Tag nbt = NBTIO.readTag(new GZIPInputStream(Files.newInputStream(path)));
|
||||
if (nbt instanceof CompoundTag)
|
||||
return (CompoundTag) nbt;
|
||||
else
|
||||
throw new IOException("level.dat malformed");
|
||||
try (InputStream is = new GZIPInputStream(Files.newInputStream(path))) {
|
||||
Tag nbt = NBTIO.readTag(is);
|
||||
if (nbt instanceof CompoundTag)
|
||||
return (CompoundTag) nbt;
|
||||
else
|
||||
throw new IOException("level.dat malformed");
|
||||
}
|
||||
}
|
||||
|
||||
public static List<World> getWorlds(Path worldDir) throws IOException {
|
||||
public static List<World> getWorlds(Path savesDir) {
|
||||
List<World> worlds = new ArrayList<>();
|
||||
for (Path world : Files.newDirectoryStream(worldDir)) {
|
||||
worlds.add(new World(world));
|
||||
try {
|
||||
if (Files.exists(savesDir))
|
||||
for (Path world : Files.newDirectoryStream(savesDir)) {
|
||||
try {
|
||||
worlds.add(new World(world));
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Failed to read world " + world, e);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Failed to read saves", e);
|
||||
}
|
||||
return worlds;
|
||||
}
|
||||
|
||||
@@ -10,9 +10,7 @@ import javafx.collections.ObservableList;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
|
||||
@@ -41,20 +39,60 @@ public class Datapack {
|
||||
|
||||
if (Files.isDirectory(datapacks))
|
||||
for (Path datapack : Files.newDirectoryStream(datapacks)) {
|
||||
if (packs.contains(FileUtils.getName(datapack)))
|
||||
if (Files.isDirectory(datapack) && packs.contains(FileUtils.getName(datapack)))
|
||||
FileUtils.deleteDirectory(datapack.toFile());
|
||||
else if (Files.isRegularFile(datapack) && packs.contains(FileUtils.getNameWithoutExtension(datapack)))
|
||||
Files.delete(datapack);
|
||||
}
|
||||
|
||||
if (isMultiple) {
|
||||
new Unzipper(path, worldPath).setReplaceExistentFile(true).unzip();
|
||||
new Unzipper(path, worldPath)
|
||||
.setReplaceExistentFile(true)
|
||||
.setFilter(new Unzipper.FileFilter() {
|
||||
@Override
|
||||
public boolean accept(Path destPath, boolean isDirectory, Path zipEntry, String entryPath) {
|
||||
// We will merge resources.zip instead of replacement.
|
||||
return !entryPath.equals("resources.zip");
|
||||
}
|
||||
})
|
||||
.unzip();
|
||||
|
||||
try (FileSystem dest = CompressingUtils.createWritableZipFileSystem(worldPath.resolve("resources.zip"));
|
||||
FileSystem zip = CompressingUtils.createReadOnlyZipFileSystem(path)) {
|
||||
Path resourcesZip = zip.getPath("resources.zip");
|
||||
if (Files.isRegularFile(resourcesZip)) {
|
||||
Path temp = Files.createTempFile("hmcl", ".zip");
|
||||
Files.copy(resourcesZip, temp, StandardCopyOption.REPLACE_EXISTING);
|
||||
try (FileSystem resources = CompressingUtils.createReadOnlyZipFileSystem(temp)) {
|
||||
FileUtils.copyDirectory(resources.getPath("/"), dest.getPath("/"));
|
||||
}
|
||||
}
|
||||
Path packMcMeta = dest.getPath("pack.mcmeta");
|
||||
Files.write(packMcMeta, Arrays.asList("{",
|
||||
"\t\"pack\": {",
|
||||
"\t\t\"pack_format\": 4,",
|
||||
"\t\t\"description\": \"Modified by HMCL.\"",
|
||||
"\t}",
|
||||
"}"), StandardOpenOption.CREATE);
|
||||
|
||||
|
||||
Path packPng = dest.getPath("pack.png");
|
||||
if (Files.isRegularFile(packPng))
|
||||
Files.delete(packPng);
|
||||
}
|
||||
} else {
|
||||
new Unzipper(path, worldPath.resolve("datapacks").resolve(FileUtils.getNameWithoutExtension(path))).unzip();
|
||||
FileUtils.copyFile(path.toFile(), datapacks.resolve(FileUtils.getName(path)).toFile());
|
||||
}
|
||||
}
|
||||
|
||||
public void deletePack(String pack) throws IOException {
|
||||
FileUtils.deleteDirectory(path.resolve(pack).toFile());
|
||||
Platform.runLater(() -> info.removeIf(p -> p.getId().equals(pack)));
|
||||
public void deletePack(Pack pack) throws IOException {
|
||||
Path subPath = pack.file;
|
||||
if (Files.isDirectory(subPath))
|
||||
FileUtils.deleteDirectory(subPath.toFile());
|
||||
else if (Files.isRegularFile(subPath))
|
||||
Files.delete(subPath);
|
||||
|
||||
Platform.runLater(() -> info.removeIf(p -> p.getId().equals(pack.getId())));
|
||||
}
|
||||
|
||||
public void loadFromZip() throws IOException {
|
||||
@@ -68,7 +106,7 @@ public class Datapack {
|
||||
isMultiple = false;
|
||||
try {
|
||||
PackMcMeta pack = JsonUtils.fromNonNullJson(FileUtils.readText(mcmeta), PackMcMeta.class);
|
||||
info.add(new Pack(mcmeta, FileUtils.getNameWithoutExtension(path), pack.getPackInfo().getDescription(), this));
|
||||
info.add(new Pack(path, FileUtils.getNameWithoutExtension(path), pack.getPackInfo().getDescription(), this));
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Failed to read datapack " + path, e);
|
||||
}
|
||||
@@ -91,20 +129,44 @@ public class Datapack {
|
||||
|
||||
if (Files.isDirectory(dir))
|
||||
for (Path subDir : Files.newDirectoryStream(dir)) {
|
||||
Path mcmeta = subDir.resolve("pack.mcmeta");
|
||||
Path mcmetaDisabled = subDir.resolve("pack.mcmeta.disabled");
|
||||
if (Files.isDirectory(subDir)) {
|
||||
Path mcmeta = subDir.resolve("pack.mcmeta");
|
||||
Path mcmetaDisabled = subDir.resolve("pack.mcmeta.disabled");
|
||||
|
||||
if (!Files.exists(mcmeta) && !Files.exists(mcmetaDisabled))
|
||||
continue;
|
||||
if (!Files.exists(mcmeta) && !Files.exists(mcmetaDisabled))
|
||||
continue;
|
||||
|
||||
boolean enabled = Files.exists(mcmeta);
|
||||
boolean enabled = Files.exists(mcmeta);
|
||||
|
||||
try {
|
||||
PackMcMeta pack = enabled ? JsonUtils.fromNonNullJson(FileUtils.readText(mcmeta), PackMcMeta.class)
|
||||
: JsonUtils.fromNonNullJson(FileUtils.readText(mcmetaDisabled), PackMcMeta.class);
|
||||
info.add(new Pack(enabled ? mcmeta : mcmetaDisabled, FileUtils.getName(subDir), pack.getPackInfo().getDescription(), this));
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Failed to read datapack " + subDir, e);
|
||||
try {
|
||||
PackMcMeta pack = enabled ? JsonUtils.fromNonNullJson(FileUtils.readText(mcmeta), PackMcMeta.class)
|
||||
: JsonUtils.fromNonNullJson(FileUtils.readText(mcmetaDisabled), PackMcMeta.class);
|
||||
info.add(new Pack(enabled ? mcmeta : mcmetaDisabled, FileUtils.getName(subDir), pack.getPackInfo().getDescription(), this));
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Failed to read datapack " + subDir, e);
|
||||
}
|
||||
} else if (Files.isRegularFile(subDir)) {
|
||||
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(subDir)) {
|
||||
Path mcmeta = fs.getPath("pack.mcmeta");
|
||||
|
||||
if (!Files.exists(mcmeta))
|
||||
continue;
|
||||
|
||||
String name = FileUtils.getName(subDir);
|
||||
boolean enabled = true;
|
||||
if (name.endsWith(".disabled")) {
|
||||
name = name.substring(0, name.length() - ".disabled".length());
|
||||
enabled = false;
|
||||
}
|
||||
if (!name.endsWith(".zip"))
|
||||
continue;
|
||||
name = StringUtils.substringBeforeLast(name, ".zip");
|
||||
|
||||
PackMcMeta pack = JsonUtils.fromNonNullJson(FileUtils.readText(mcmeta), PackMcMeta.class);
|
||||
info.add(new Pack(subDir, name, pack.getPackInfo().getDescription(), this));
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Failed to read datapack " + subDir, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,22 +174,22 @@ public class Datapack {
|
||||
}
|
||||
|
||||
public static class Pack {
|
||||
private Path packMcMeta;
|
||||
private Path file;
|
||||
private final BooleanProperty active;
|
||||
private final String id;
|
||||
private final String description;
|
||||
private final Datapack datapack;
|
||||
|
||||
public Pack(Path packMcMeta, String id, String description, Datapack datapack) {
|
||||
this.packMcMeta = packMcMeta;
|
||||
public Pack(Path file, String id, String description, Datapack datapack) {
|
||||
this.file = file;
|
||||
this.id = id;
|
||||
this.description = description;
|
||||
this.datapack = datapack;
|
||||
|
||||
active = new SimpleBooleanProperty(this, "active", !DISABLED_EXT.equals(FileUtils.getExtension(packMcMeta))) {
|
||||
active = new SimpleBooleanProperty(this, "active", !DISABLED_EXT.equals(FileUtils.getExtension(file))) {
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
Path f = Pack.this.packMcMeta.toAbsolutePath(), newF;
|
||||
Path f = Pack.this.file.toAbsolutePath(), newF;
|
||||
if (DISABLED_EXT.equals(FileUtils.getExtension(f)))
|
||||
newF = f.getParent().resolve(FileUtils.getNameWithoutExtension(f));
|
||||
else
|
||||
@@ -135,7 +197,7 @@ public class Datapack {
|
||||
|
||||
try {
|
||||
Files.move(f, newF);
|
||||
Pack.this.packMcMeta = newF;
|
||||
Pack.this.file = newF;
|
||||
} catch (IOException e) {
|
||||
// Mod file is occupied.
|
||||
Logging.LOG.warning("Unable to rename file " + f + " to " + newF);
|
||||
|
||||
@@ -301,7 +301,13 @@ public abstract class Task {
|
||||
return finalized(scheduler, (variables, isDependentsSucceeded) -> {
|
||||
if (isDependentsSucceeded) {
|
||||
if (success != null)
|
||||
success.accept(variables);
|
||||
try {
|
||||
success.accept(variables);
|
||||
} catch (Exception e) {
|
||||
Logging.LOG.log(Level.WARNING, "Failed to execute " + success, e);
|
||||
if (failure != null)
|
||||
failure.accept(e);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (failure != null)
|
||||
|
||||
@@ -73,6 +73,8 @@ public final class CompressingUtils {
|
||||
} catch (ZipError error) {
|
||||
// Since Java 8 throws ZipError stupidly
|
||||
throw new ZipException(error.getMessage());
|
||||
} catch (UnsupportedOperationException ex) {
|
||||
throw new IOException("Not a zip file", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -118,7 +118,7 @@ public final class FileUtils {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
Path destFile = dest.resolve(src.relativize(file));
|
||||
Files.copy(file, destFile);
|
||||
Files.copy(file, destFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
@@ -126,7 +126,7 @@ public final class FileUtils {
|
||||
@Override
|
||||
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
|
||||
Path destDir = dest.resolve(src.relativize(dir));
|
||||
Files.createDirectory(destDir);
|
||||
Files.createDirectories(destDir);
|
||||
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user