重构游戏目录管理逻辑 (#6273)
This commit is contained in:
1 parent
d8938e1fcb
commit
7580c6da82
65 files changed
+1651
-1527
No files matched your search
@@ -158,7 +158,7 @@ public final class Launcher extends Application {
|
||||
DownloadProviders.init();
|
||||
ProxyManager.init();
|
||||
Accounts.init();
|
||||
Profiles.init();
|
||||
GameDirectoryManager.init();
|
||||
AuthlibInjectorServers.init();
|
||||
AnimationUtils.init();
|
||||
|
||||
|
||||
@@ -20,8 +20,12 @@ package org.jackhuang.hmcl.game;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.StringBinding;
|
||||
import javafx.scene.image.Image;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.event.Event;
|
||||
import org.jackhuang.hmcl.event.EventManager;
|
||||
@@ -33,10 +37,11 @@ import org.jackhuang.hmcl.modpack.ModpackProvider;
|
||||
import org.jackhuang.hmcl.setting.LauncherSettings;
|
||||
import org.jackhuang.hmcl.setting.SettingsManager;
|
||||
import org.jackhuang.hmcl.setting.DefaultIsolationType;
|
||||
import org.jackhuang.hmcl.setting.DownloadProviders;
|
||||
import org.jackhuang.hmcl.setting.GameSettings;
|
||||
import org.jackhuang.hmcl.setting.GameWindowType;
|
||||
import org.jackhuang.hmcl.setting.LegacyGameSettingsMigrator;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.GameDirectory;
|
||||
import org.jackhuang.hmcl.setting.ProxyType;
|
||||
import org.jackhuang.hmcl.setting.SettingFileUtils;
|
||||
import org.jackhuang.hmcl.setting.GameSettingsPresetID;
|
||||
@@ -68,9 +73,17 @@ import static org.jackhuang.hmcl.setting.SettingsManager.settings;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
/// HMCL game repository implementation backed by a profile and per-instance game settings.
|
||||
/// HMCL game repository implementation backed by a GameDirectory and per-instance game settings.
|
||||
@NotNullByDefault
|
||||
public final class HMCLGameRepository extends DefaultGameRepository {
|
||||
/// References an optional game instance in a repository.
|
||||
///
|
||||
/// @param repository the owning game repository
|
||||
/// @param instanceId the game instance ID, or `null` when only repository context is available
|
||||
@NotNullByDefault
|
||||
public record InstanceReference(HMCLGameRepository repository, @Nullable String instanceId) {
|
||||
}
|
||||
|
||||
/// Directory under the version root that stores HMCL-managed instance metadata.
|
||||
private static final String INSTANCE_METADATA_DIRECTORY = ".hmcl";
|
||||
|
||||
@@ -83,7 +96,11 @@ public final class HMCLGameRepository extends DefaultGameRepository {
|
||||
/// Current file name for instance-specific game settings.
|
||||
private static final String INSTANCE_GAME_SETTINGS_FILENAME = "instance-game-settings.json";
|
||||
|
||||
private final Profile profile;
|
||||
/// The persistent game directory for this repository.
|
||||
private final GameDirectory gameDirectory;
|
||||
|
||||
/// The selected instance ID persisted for this repository's game directory.
|
||||
private final StringBinding selectedInstance;
|
||||
|
||||
// instance game settings
|
||||
private final Map<String, GameSettings.Instance> instanceGameSettings = new HashMap<>();
|
||||
@@ -94,13 +111,54 @@ public final class HMCLGameRepository extends DefaultGameRepository {
|
||||
|
||||
public final EventManager<Event> onVersionIconChanged = new EventManager<>();
|
||||
|
||||
public HMCLGameRepository(Profile profile, Path baseDirectory) {
|
||||
super(baseDirectory);
|
||||
this.profile = profile;
|
||||
/// Creates a repository backed by the given game directory.
|
||||
public HMCLGameRepository(GameDirectory gameDirectory) {
|
||||
super(gameDirectory.getPath().toPath());
|
||||
this.gameDirectory = gameDirectory;
|
||||
this.selectedInstance = Bindings.stringValueAt(settings().getSelectedInstance(), gameDirectory.getId());
|
||||
gameDirectory.pathProperty().addListener((a, b, newValue) -> changeDirectory(newValue.toPath()));
|
||||
}
|
||||
|
||||
public Profile getProfile() {
|
||||
return profile;
|
||||
/// Returns the persistent game directory for this repository.
|
||||
public GameDirectory getGameDirectory() {
|
||||
return gameDirectory;
|
||||
}
|
||||
|
||||
/// Returns the selected instance ID property for this repository's game directory.
|
||||
public StringBinding selectedInstanceProperty() {
|
||||
return selectedInstance;
|
||||
}
|
||||
|
||||
/// Returns the selected instance ID for this repository's game directory.
|
||||
public @Nullable String getSelectedInstance() {
|
||||
return selectedInstance.get();
|
||||
}
|
||||
|
||||
/// Sets the selected instance ID for this repository's game directory.
|
||||
public void setSelectedInstance(@Nullable String instance) {
|
||||
settings().setSelectedInstance(gameDirectory.getId(), instance);
|
||||
}
|
||||
|
||||
/// Refreshes the selected instance ID after versions are loaded.
|
||||
public void refreshSelectedInstance() {
|
||||
@Nullable String selectedInstance = settings().getSelectedInstance(gameDirectory.getId());
|
||||
@Nullable String refreshedInstance = selectedInstance;
|
||||
if (!hasVersion(refreshedInstance)) {
|
||||
refreshedInstance = getVersions().isEmpty() ? null : getVersions().iterator().next().getId();
|
||||
}
|
||||
if (!Objects.equals(selectedInstance, refreshedInstance)) {
|
||||
setSelectedInstance(refreshedInstance);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a dependency manager using the currently selected download provider.
|
||||
public DefaultDependencyManager getDependency() {
|
||||
return getDependency(DownloadProviders.getDownloadProvider());
|
||||
}
|
||||
|
||||
/// Returns a dependency manager using the given download provider.
|
||||
public DefaultDependencyManager getDependency(DownloadProvider downloadProvider) {
|
||||
return new DefaultDependencyManager(this, downloadProvider, HMCLCacheRepository.REPOSITORY);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -294,8 +352,8 @@ public final class HMCLGameRepository extends DefaultGameRepository {
|
||||
return;
|
||||
}
|
||||
|
||||
GameSettings.Preset profilePreset = SettingsManager.getGameSettings(profile.getLegacyGameSettings());
|
||||
if (profilePreset != null && profilePreset.defaultIsolationTypeProperty().getValue() == DefaultIsolationType.ALWAYS) {
|
||||
GameSettings.Preset gameDirectoryPreset = SettingsManager.getGameSettings(gameDirectory.getLegacyGameSettings());
|
||||
if (gameDirectoryPreset != null && gameDirectoryPreset.defaultIsolationTypeProperty().getValue() == DefaultIsolationType.ALWAYS) {
|
||||
GameSettings.Instance setting = new GameSettings.Instance();
|
||||
setting.getOverrideProperties().add(GameSettings.PROPERTY_RUNNING_DIRECTORY);
|
||||
initInstanceGameSettings(id, setting);
|
||||
@@ -461,7 +519,7 @@ public final class HMCLGameRepository extends DefaultGameRepository {
|
||||
public GameSettings.Preset getParentGameSettings(@Nullable GameSettings.Instance instance) {
|
||||
@Nullable GameSettingsPresetID parent = instance != null && instance.parentProperty().getValue() != null
|
||||
? instance.parentProperty().getValue()
|
||||
: profile.getLegacyGameSettings();
|
||||
: gameDirectory.getLegacyGameSettings();
|
||||
GameSettings.Preset parentSetting = SettingsManager.getGameSettings(parent);
|
||||
return parentSetting != null ? parentSetting : SettingsManager.getDefaultGameSettingsPresetOrCreate();
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import org.jackhuang.hmcl.modpack.MinecraftInstanceTask;
|
||||
import org.jackhuang.hmcl.modpack.Modpack;
|
||||
import org.jackhuang.hmcl.modpack.ModpackConfiguration;
|
||||
import org.jackhuang.hmcl.modpack.ModpackInstallTask;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
@@ -45,9 +44,9 @@ public final class HMCLModpackInstallTask extends Task<Void> {
|
||||
private final List<Task<?>> dependencies = new ArrayList<>(1);
|
||||
private final List<Task<?>> dependents = new ArrayList<>(4);
|
||||
|
||||
public HMCLModpackInstallTask(Profile profile, Path zipFile, Modpack modpack, String name) {
|
||||
dependency = profile.getDependency();
|
||||
repository = profile.getRepository();
|
||||
public HMCLModpackInstallTask(HMCLGameRepository repository, Path zipFile, Modpack modpack, String name) {
|
||||
this.repository = repository;
|
||||
this.dependency = repository.getDependency();
|
||||
this.zipFile = zipFile;
|
||||
this.name = name;
|
||||
this.modpack = modpack;
|
||||
|
||||
@@ -24,7 +24,6 @@ import org.jackhuang.hmcl.modpack.MismatchedModpackTypeException;
|
||||
import org.jackhuang.hmcl.modpack.Modpack;
|
||||
import org.jackhuang.hmcl.modpack.ModpackProvider;
|
||||
import org.jackhuang.hmcl.modpack.ModpackUpdateTask;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
@@ -56,9 +55,7 @@ public final class HMCLModpackProvider implements ModpackProvider {
|
||||
throw new IllegalArgumentException("HMCLModpackProvider requires HMCLGameRepository");
|
||||
}
|
||||
|
||||
Profile profile = repository.getProfile();
|
||||
|
||||
return new ModpackUpdateTask(dependencyManager.getGameRepository(), name, new HMCLModpackInstallTask(profile, zipFile, modpack, name));
|
||||
return new ModpackUpdateTask(dependencyManager.getGameRepository(), name, new HMCLModpackInstallTask(repository, zipFile, modpack, name));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -80,7 +77,7 @@ public final class HMCLModpackProvider implements ModpackProvider {
|
||||
private final static class HMCLModpack extends Modpack {
|
||||
@Override
|
||||
public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, String name, String iconUrl) {
|
||||
return new HMCLModpackInstallTask(((HMCLGameRepository) dependencyManager.getGameRepository()).getProfile(), zipFile, this, name);
|
||||
return new HMCLModpackInstallTask((HMCLGameRepository) dependencyManager.getGameRepository(), zipFile, this, name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ public final class LauncherHelper {
|
||||
|
||||
private static final String LWJGL_3_4_1_TIP = "lwjgl3.4.1-ffm";
|
||||
|
||||
private final Profile profile;
|
||||
private final HMCLGameRepository repository;
|
||||
private Account account;
|
||||
private final String selectedVersion;
|
||||
private Path scriptFile;
|
||||
@@ -87,11 +87,11 @@ public final class LauncherHelper {
|
||||
private QuickPlayOption quickPlayOption;
|
||||
private boolean disableOfflineSkin = false;
|
||||
|
||||
public LauncherHelper(Profile profile, Account account, String selectedVersion) {
|
||||
this.profile = Objects.requireNonNull(profile);
|
||||
public LauncherHelper(HMCLGameRepository repository, Account account, String selectedVersion) {
|
||||
this.repository = Objects.requireNonNull(repository);
|
||||
this.account = Objects.requireNonNull(account);
|
||||
this.selectedVersion = Objects.requireNonNull(selectedVersion);
|
||||
this.setting = profile.getRepository().getEffectiveGameSettings(selectedVersion);
|
||||
this.setting = repository.getEffectiveGameSettings(selectedVersion);
|
||||
this.launcherVisibility = setting.getInheritable(GameSettings::launcherVisibilityProperty);
|
||||
this.showLogs = setting.getInheritable(GameSettings::showLogsProperty);
|
||||
this.launchingStepsPane.setTitle(i18n("version.launch"));
|
||||
@@ -142,8 +142,7 @@ public final class LauncherHelper {
|
||||
// https://github.com/HMCL-dev/HMCL/pull/4121
|
||||
PROCESSES.removeIf(it -> it.get() == null);
|
||||
|
||||
HMCLGameRepository repository = profile.getRepository();
|
||||
DefaultDependencyManager dependencyManager = profile.getDependency();
|
||||
DefaultDependencyManager dependencyManager = repository.getDependency();
|
||||
AtomicReference<Version> version = new AtomicReference<>(MaintainTask.maintain(repository, repository.getResolvedVersion(selectedVersion)));
|
||||
Optional<String> gameVersion = repository.getGameVersion(version.get());
|
||||
boolean integrityCheck = repository.unmarkVersionLaunchedAbnormally(selectedVersion);
|
||||
@@ -153,7 +152,7 @@ public final class LauncherHelper {
|
||||
|
||||
AtomicReference<JavaRuntime> javaVersionRef = new AtomicReference<>();
|
||||
|
||||
TaskExecutor executor = checkGameState(profile, setting, version.get())
|
||||
TaskExecutor executor = checkGameState(repository, setting, version.get())
|
||||
.thenComposeAsync(java -> {
|
||||
javaVersionRef.set(Objects.requireNonNull(java));
|
||||
version.set(NativePatcher.patchNative(repository, version.get(), gameVersion.orElse(null), java, setting, javaArguments));
|
||||
@@ -224,7 +223,7 @@ public final class LauncherHelper {
|
||||
.thenComposeAsync(() -> logIn(account).withStage("launch.state.logging_in"))
|
||||
.thenComposeAsync(authInfo -> Task.supplyAsync(() -> {
|
||||
LaunchOptions.Builder launchOptionsBuilder = repository.getLaunchOptions(
|
||||
selectedVersion, javaVersionRef.get(), profile.getPath().toPath(), javaAgents, javaArguments, scriptFile != null);
|
||||
selectedVersion, javaVersionRef.get(), repository.getBaseDirectory(), javaAgents, javaArguments, scriptFile != null);
|
||||
if (disableOfflineSkin) {
|
||||
launchOptionsBuilder.setDaemon(false);
|
||||
}
|
||||
@@ -382,8 +381,8 @@ public final class LauncherHelper {
|
||||
executor.start();
|
||||
}
|
||||
|
||||
private static Task<JavaRuntime> checkGameState(Profile profile, GameSettings.Effective setting, Version version) {
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version, profile.getRepository().getGameVersion(version).orElse(null));
|
||||
private static Task<JavaRuntime> checkGameState(HMCLGameRepository repository, GameSettings.Effective setting, Version version) {
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version, repository.getGameVersion(version).orElse(null));
|
||||
GameVersionNumber gameVersion = GameVersionNumber.asGameVersion(analyzer.getVersion(LibraryAnalyzer.LibraryType.MINECRAFT));
|
||||
|
||||
Task<JavaRuntime> getJavaTask = Task.supplyAsync(() -> {
|
||||
@@ -450,7 +449,7 @@ public final class LauncherHelper {
|
||||
}
|
||||
|
||||
if (targetJavaVersion != null && supportedVersions.contains(targetJavaVersion)) {
|
||||
downloadJava(targetJavaVersion, profile)
|
||||
downloadJava(targetJavaVersion, repository)
|
||||
.whenCompleteAsync((downloadedJava, exception) -> {
|
||||
if (exception == null) {
|
||||
future.complete(downloadedJava);
|
||||
@@ -527,7 +526,7 @@ public final class LauncherHelper {
|
||||
gameJavaVersion = null;
|
||||
|
||||
if (gameJavaVersion != null) {
|
||||
FXUtils.runInFX(() -> downloadJava(gameJavaVersion, profile).whenCompleteAsync((downloadedJava, throwable) -> {
|
||||
FXUtils.runInFX(() -> downloadJava(gameJavaVersion, repository).whenCompleteAsync((downloadedJava, throwable) -> {
|
||||
if (throwable == null) {
|
||||
setting.setJavaAutoSelected();
|
||||
future.complete(downloadedJava);
|
||||
@@ -685,14 +684,14 @@ public final class LauncherHelper {
|
||||
return task.withStage("launch.state.java");
|
||||
}
|
||||
|
||||
private static CompletableFuture<JavaRuntime> downloadJava(GameJavaVersion javaVersion, Profile profile) {
|
||||
private static CompletableFuture<JavaRuntime> downloadJava(GameJavaVersion javaVersion, HMCLGameRepository repository) {
|
||||
CompletableFuture<JavaRuntime> future = new CompletableFuture<>();
|
||||
Controllers.dialog(new MessageDialogPane.Builder(
|
||||
i18n("launch.advice.require_newer_java_version", javaVersion.majorVersion()),
|
||||
i18n("message.warning"),
|
||||
MessageType.QUESTION)
|
||||
.yesOrNo(() -> {
|
||||
DownloadProvider downloadProvider = profile.getDependency().getDownloadProvider();
|
||||
DownloadProvider downloadProvider = repository.getDependency().getDownloadProvider();
|
||||
Controllers.taskDialog(JavaManager.getDownloadJavaTask(downloadProvider, SYSTEM_PLATFORM, javaVersion)
|
||||
.whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
if (exception == null) {
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.io.Unzipper;
|
||||
@@ -29,13 +28,11 @@ import java.nio.file.Paths;
|
||||
|
||||
public class ManuallyCreatedModpackInstallTask extends Task<Path> {
|
||||
|
||||
private final Profile profile;
|
||||
private final Path zipFile;
|
||||
private final Charset charset;
|
||||
private final String name;
|
||||
|
||||
public ManuallyCreatedModpackInstallTask(Profile profile, Path zipFile, Charset charset, String name) {
|
||||
this.profile = profile;
|
||||
public ManuallyCreatedModpackInstallTask(Path zipFile, Charset charset, String name) {
|
||||
this.zipFile = zipFile;
|
||||
this.charset = charset;
|
||||
this.name = name;
|
||||
|
||||
@@ -34,8 +34,8 @@ import org.jackhuang.hmcl.modpack.server.ServerModpackRemoteInstallTask;
|
||||
import org.jackhuang.hmcl.setting.GameSettings;
|
||||
import org.jackhuang.hmcl.setting.GameWindowType;
|
||||
import org.jackhuang.hmcl.setting.JavaVersionType;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.GameDirectory;
|
||||
import org.jackhuang.hmcl.setting.GameDirectoryManager;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
@@ -156,11 +156,10 @@ public final class ModpackHelper {
|
||||
}
|
||||
}
|
||||
|
||||
public static Task<?> getInstallTask(Profile profile, ServerModpackManifest manifest, String name, Modpack modpack) {
|
||||
profile.getRepository().markVersionAsModpack(name);
|
||||
public static Task<?> getInstallTask(HMCLGameRepository repository, ServerModpackManifest manifest, String name, Modpack modpack) {
|
||||
repository.markVersionAsModpack(name);
|
||||
|
||||
ExceptionalRunnable<?> success = () -> {
|
||||
HMCLGameRepository repository = profile.getRepository();
|
||||
repository.refreshVersions();
|
||||
GameSettings.Instance setting = repository.getInstanceGameSettingsOrCreate(name);
|
||||
repository.undoMark(name);
|
||||
@@ -176,7 +175,7 @@ public final class ModpackHelper {
|
||||
}
|
||||
};
|
||||
|
||||
return new ServerModpackRemoteInstallTask(profile.getDependency(), manifest, name)
|
||||
return new ServerModpackRemoteInstallTask(repository.getDependency(), manifest, name)
|
||||
.whenComplete(Schedulers.defaultScheduler(), success, failure)
|
||||
.withStagesHints(new Task.StagesHint("hmcl.modpack"), new Task.StagesHint("hmcl.modpack.download", List.of("hmcl.install.assets", "hmcl.install.libraries")));
|
||||
}
|
||||
@@ -185,27 +184,26 @@ public final class ModpackHelper {
|
||||
return Files.exists(Paths.get("externalgames").resolve(name));
|
||||
}
|
||||
|
||||
public static Task<?> getInstallManuallyCreatedModpackTask(Profile profile, Path zipFile, String name, Charset charset) {
|
||||
public static Task<?> getInstallManuallyCreatedModpackTask(Path zipFile, String name, Charset charset) {
|
||||
if (isExternalGameNameConflicts(name)) {
|
||||
throw new IllegalArgumentException("name existing");
|
||||
}
|
||||
|
||||
return new ManuallyCreatedModpackInstallTask(profile, zipFile, charset, name)
|
||||
return new ManuallyCreatedModpackInstallTask(zipFile, charset, name)
|
||||
.thenAcceptAsync(Schedulers.javafx(), location -> {
|
||||
Profile newProfile = new Profile(
|
||||
Profiles.newProfileId(),
|
||||
GameDirectory newGameDirectory = new GameDirectory(
|
||||
GameDirectoryManager.newGameDirectoryId(),
|
||||
LocalizedText.plain(name),
|
||||
PortablePath.fromPath(location));
|
||||
Profiles.addLocalProfile(newProfile);
|
||||
Profiles.setSelectedProfile(newProfile);
|
||||
GameDirectoryManager.addLocalGameDirectory(newGameDirectory);
|
||||
GameDirectoryManager.setSelectedGameDirectory(newGameDirectory);
|
||||
});
|
||||
}
|
||||
|
||||
public static Task<?> getInstallTask(Profile profile, Path zipFile, String name, Modpack modpack, String iconUrl) {
|
||||
profile.getRepository().markVersionAsModpack(name);
|
||||
public static Task<?> getInstallTask(HMCLGameRepository repository, Path zipFile, String name, Modpack modpack, String iconUrl) {
|
||||
repository.markVersionAsModpack(name);
|
||||
|
||||
ExceptionalRunnable<?> success = () -> {
|
||||
HMCLGameRepository repository = profile.getRepository();
|
||||
repository.refreshVersions();
|
||||
GameSettings.Instance setting = repository.getInstanceGameSettingsOrCreate(name);
|
||||
repository.undoMark(name);
|
||||
@@ -222,45 +220,45 @@ public final class ModpackHelper {
|
||||
};
|
||||
|
||||
if (modpack.getManifest() instanceof MultiMCInstanceConfiguration)
|
||||
return modpack.getInstallTask(profile.getDependency(), zipFile, name, iconUrl)
|
||||
return modpack.getInstallTask(repository.getDependency(), zipFile, name, iconUrl)
|
||||
.whenComplete(Schedulers.defaultScheduler(), success, failure)
|
||||
.thenComposeAsync(createMultiMCPostInstallTask(profile, (MultiMCInstanceConfiguration) modpack.getManifest(), name))
|
||||
.thenComposeAsync(createMultiMCPostInstallTask(repository, (MultiMCInstanceConfiguration) modpack.getManifest(), name))
|
||||
.withStagesHints(new Task.StagesHint("hmcl.modpack"), new Task.StagesHint("hmcl.modpack.download", List.of("hmcl.install.assets", "hmcl.install.libraries")));
|
||||
else if (modpack.getManifest() instanceof McbbsModpackManifest)
|
||||
return modpack.getInstallTask(profile.getDependency(), zipFile, name, iconUrl)
|
||||
return modpack.getInstallTask(repository.getDependency(), zipFile, name, iconUrl)
|
||||
.whenComplete(Schedulers.defaultScheduler(), success, failure)
|
||||
.thenComposeAsync(createMcbbsPostInstallTask(profile, (McbbsModpackManifest) modpack.getManifest(), name))
|
||||
.thenComposeAsync(createMcbbsPostInstallTask(repository, (McbbsModpackManifest) modpack.getManifest(), name))
|
||||
.withStagesHints(new Task.StagesHint("hmcl.modpack"), new Task.StagesHint("hmcl.modpack.download", List.of("hmcl.install.assets", "hmcl.install.libraries")));
|
||||
else
|
||||
return modpack.getInstallTask(profile.getDependency(), zipFile, name, iconUrl)
|
||||
return modpack.getInstallTask(repository.getDependency(), zipFile, name, iconUrl)
|
||||
.whenComplete(Schedulers.javafx(), success, failure)
|
||||
.withStagesHints(new Task.StagesHint("hmcl.modpack"), new Task.StagesHint("hmcl.modpack.download", List.of("hmcl.install.assets", "hmcl.install.libraries")));
|
||||
}
|
||||
|
||||
public static Task<Void> getUpdateTask(Profile profile, ServerModpackManifest manifest, Charset charset, String name, ModpackConfiguration<?> configuration) throws UnsupportedModpackException {
|
||||
public static Task<Void> getUpdateTask(HMCLGameRepository repository, ServerModpackManifest manifest, Charset charset, String name, ModpackConfiguration<?> configuration) throws UnsupportedModpackException {
|
||||
switch (configuration.getType()) {
|
||||
case ServerModpackRemoteInstallTask.MODPACK_TYPE:
|
||||
return new ModpackUpdateTask(profile.getRepository(), name, new ServerModpackRemoteInstallTask(profile.getDependency(), manifest, name))
|
||||
.thenComposeAsync(profile.getRepository().refreshVersionsAsync())
|
||||
return new ModpackUpdateTask(repository, name, new ServerModpackRemoteInstallTask(repository.getDependency(), manifest, name))
|
||||
.thenComposeAsync(repository.refreshVersionsAsync())
|
||||
.withStagesHints(new Task.StagesHint("hmcl.modpack"), new Task.StagesHint("hmcl.modpack.download", List.of("hmcl.install.assets", "hmcl.install.libraries")));
|
||||
default:
|
||||
throw new UnsupportedModpackException();
|
||||
}
|
||||
}
|
||||
|
||||
public static Task<?> getUpdateTask(Profile profile, Path zipFile, Charset charset, String name, ModpackConfiguration<?> configuration) throws UnsupportedModpackException, ManuallyCreatedModpackException, MismatchedModpackTypeException {
|
||||
public static Task<?> getUpdateTask(HMCLGameRepository repository, Path zipFile, Charset charset, String name, ModpackConfiguration<?> configuration) throws UnsupportedModpackException, ManuallyCreatedModpackException, MismatchedModpackTypeException {
|
||||
Modpack modpack = ModpackHelper.readModpackManifest(zipFile, charset);
|
||||
ModpackProvider provider = getProviderByType(configuration.getType());
|
||||
if (provider == null) {
|
||||
throw new UnsupportedModpackException();
|
||||
}
|
||||
if (modpack.getManifest() instanceof MultiMCInstanceConfiguration)
|
||||
return provider.createUpdateTask(profile.getDependency(), name, zipFile, modpack)
|
||||
.thenComposeAsync(() -> createMultiMCPostUpdateTask(profile, (MultiMCInstanceConfiguration) modpack.getManifest(), name))
|
||||
.thenComposeAsync(profile.getRepository().refreshVersionsAsync());
|
||||
return provider.createUpdateTask(repository.getDependency(), name, zipFile, modpack)
|
||||
.thenComposeAsync(() -> createMultiMCPostUpdateTask(repository, (MultiMCInstanceConfiguration) modpack.getManifest(), name))
|
||||
.thenComposeAsync(repository.refreshVersionsAsync());
|
||||
else
|
||||
return provider.createUpdateTask(profile.getDependency(), name, zipFile, modpack)
|
||||
.thenComposeAsync(profile.getRepository().refreshVersionsAsync());
|
||||
return provider.createUpdateTask(repository.getDependency(), name, zipFile, modpack)
|
||||
.thenComposeAsync(repository.refreshVersionsAsync());
|
||||
}
|
||||
|
||||
public static void toGameSettings(MultiMCInstanceConfiguration c, GameSettings.Instance setting) {
|
||||
@@ -335,23 +333,22 @@ public final class ModpackHelper {
|
||||
}
|
||||
}
|
||||
|
||||
private static Task<Void> createMultiMCPostUpdateTask(Profile profile, MultiMCInstanceConfiguration manifest, String version) {
|
||||
private static Task<Void> createMultiMCPostUpdateTask(HMCLGameRepository repository, MultiMCInstanceConfiguration manifest, String version) {
|
||||
return Task.runAsync(Schedulers.javafx(), () -> {
|
||||
GameSettings.Instance setting = Objects.requireNonNull(profile.getRepository().getInstanceGameSettingsOrCreate(version));
|
||||
GameSettings.Instance setting = Objects.requireNonNull(repository.getInstanceGameSettingsOrCreate(version));
|
||||
ModpackHelper.applyCommandAndJvmSettings(manifest, setting);
|
||||
});
|
||||
}
|
||||
|
||||
private static Task<Void> createMultiMCPostInstallTask(Profile profile, MultiMCInstanceConfiguration manifest, String version) {
|
||||
private static Task<Void> createMultiMCPostInstallTask(HMCLGameRepository repository, MultiMCInstanceConfiguration manifest, String version) {
|
||||
return Task.runAsync(Schedulers.javafx(), () -> {
|
||||
GameSettings.Instance setting = Objects.requireNonNull(profile.getRepository().getInstanceGameSettingsOrCreate(version));
|
||||
GameSettings.Instance setting = Objects.requireNonNull(repository.getInstanceGameSettingsOrCreate(version));
|
||||
ModpackHelper.toGameSettings(manifest, setting);
|
||||
});
|
||||
}
|
||||
|
||||
private static Task<Void> createMcbbsPostInstallTask(Profile profile, McbbsModpackManifest manifest, String version) {
|
||||
private static Task<Void> createMcbbsPostInstallTask(HMCLGameRepository repository, McbbsModpackManifest manifest, String version) {
|
||||
return Task.runAsync(Schedulers.javafx(), () -> {
|
||||
HMCLGameRepository repository = profile.getRepository();
|
||||
GameSettings.Effective effective = repository.getEffectiveGameSettings(version);
|
||||
if (manifest.getLaunchInfo().getMinMemory() > effective.getMaxMemory()) {
|
||||
GameSettings.Instance setting = Objects.requireNonNull(repository.getInstanceGameSettingsOrCreate(version));
|
||||
|
||||
@@ -102,14 +102,14 @@ public final class GameDirectories extends ObservableSetting implements JsonSche
|
||||
|
||||
/// Game directories stored in this file.
|
||||
@SerializedName("directories")
|
||||
private final ObservableList<Profile> gameDirectories =
|
||||
private final ObservableList<GameDirectory> gameDirectories =
|
||||
FXCollections.observableArrayList(profile -> new Observable[] { profile });
|
||||
|
||||
/// Whether this store represents `HMCL_USER_HOME/config/user-game-directories.json`.
|
||||
private transient boolean userFile;
|
||||
|
||||
/// Returns the game directories stored in this file.
|
||||
public ObservableList<Profile> getGameDirectories() {
|
||||
public ObservableList<GameDirectory> getGameDirectories() {
|
||||
return gameDirectories;
|
||||
}
|
||||
|
||||
|
||||
+28
-46
@@ -24,10 +24,6 @@ import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.game.HMCLCacheRepository;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.util.PortablePath;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
import org.jackhuang.hmcl.util.i18n.LocalizedText;
|
||||
@@ -40,15 +36,10 @@ import java.util.Objects;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
@JsonAdapter(Profile.Serializer.class)
|
||||
/// Persistent configuration for a game directory.
|
||||
@JsonAdapter(GameDirectory.Serializer.class)
|
||||
@NotNullByDefault
|
||||
public final class Profile implements Observable {
|
||||
private final HMCLGameRepository repository;
|
||||
|
||||
public final class GameDirectory implements Observable {
|
||||
/// The stable game directory ID.
|
||||
private final GameDirectoryID id;
|
||||
|
||||
@@ -75,25 +66,25 @@ public final class Profile implements Observable {
|
||||
this.path.set(Objects.requireNonNull(path));
|
||||
}
|
||||
|
||||
/// The custom localized profile name, or `null` for profiles without a stored name.
|
||||
/// The custom localized game directory name, or `null` when no name is stored.
|
||||
private final ObjectProperty<@Nullable LocalizedText> name;
|
||||
|
||||
/// Returns the custom localized profile name property.
|
||||
/// Returns the custom localized game directory name property.
|
||||
public ObjectProperty<@Nullable LocalizedText> nameProperty() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/// Returns the custom localized profile name, or `null` when no name is stored.
|
||||
/// Returns the custom localized game directory name, or `null` when no name is stored.
|
||||
public @Nullable LocalizedText getName() {
|
||||
return name.get();
|
||||
}
|
||||
|
||||
/// Sets the custom localized profile name.
|
||||
/// Sets the custom localized game directory name.
|
||||
public void setName(@Nullable LocalizedText name) {
|
||||
this.name.set(name);
|
||||
}
|
||||
|
||||
/// The migrated legacy game settings preset ID, or `null` when this profile uses the default preset.
|
||||
/// The migrated legacy game settings preset ID, or `null` when this game directory uses the default preset.
|
||||
private final ObjectProperty<@Nullable GameSettingsPresetID> legacyGameSettings;
|
||||
|
||||
/// Returns the migrated legacy game settings preset ID property.
|
||||
@@ -101,7 +92,7 @@ public final class Profile implements Observable {
|
||||
return legacyGameSettings;
|
||||
}
|
||||
|
||||
/// Returns the migrated legacy game settings preset ID, or `null` when this profile uses the default preset.
|
||||
/// Returns the migrated legacy game settings preset ID, or `null` when this game directory uses the default preset.
|
||||
public @Nullable GameSettingsPresetID getLegacyGameSettings() {
|
||||
return legacyGameSettings.get();
|
||||
}
|
||||
@@ -111,13 +102,13 @@ public final class Profile implements Observable {
|
||||
this.legacyGameSettings.set(legacyGameSettings);
|
||||
}
|
||||
|
||||
/// Creates a profile.
|
||||
public Profile(GameDirectoryID id, @Nullable LocalizedText name, PortablePath path) {
|
||||
/// Creates a game directory.
|
||||
public GameDirectory(GameDirectoryID id, @Nullable LocalizedText name, PortablePath path) {
|
||||
this(id, name, path, null);
|
||||
}
|
||||
|
||||
/// Creates a profile.
|
||||
public Profile(
|
||||
/// Creates a game directory.
|
||||
public GameDirectory(
|
||||
GameDirectoryID id,
|
||||
@Nullable LocalizedText name,
|
||||
PortablePath path,
|
||||
@@ -126,25 +117,11 @@ public final class Profile implements Observable {
|
||||
this.name = new SimpleObjectProperty<>(this, "name", name);
|
||||
this.path = new SimpleObjectProperty<>(this, "path", Objects.requireNonNull(path));
|
||||
this.legacyGameSettings = new SimpleObjectProperty<>(this, "legacyGameSettings", legacyGameSettings);
|
||||
repository = new HMCLGameRepository(this, path.toPath());
|
||||
|
||||
this.path.addListener((a, b, newValue) -> repository.changeDirectory(newValue.toPath()));
|
||||
|
||||
addPropertyChangedListener(onInvalidating(this::invalidate));
|
||||
}
|
||||
|
||||
public HMCLGameRepository getRepository() {
|
||||
return repository;
|
||||
}
|
||||
|
||||
public DefaultDependencyManager getDependency() {
|
||||
return getDependency(DownloadProviders.getDownloadProvider());
|
||||
}
|
||||
|
||||
public DefaultDependencyManager getDependency(DownloadProvider downloadProvider) {
|
||||
return new DefaultDependencyManager(repository, downloadProvider, HMCLCacheRepository.REPOSITORY);
|
||||
}
|
||||
|
||||
/// Returns a debug string containing the game directory path and display metadata.
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this)
|
||||
@@ -153,25 +130,29 @@ public final class Profile implements Observable {
|
||||
.toString();
|
||||
}
|
||||
|
||||
/// Registers a listener that invalidates this game directory when any stored property changes.
|
||||
private void addPropertyChangedListener(InvalidationListener listener) {
|
||||
name.addListener(listener);
|
||||
path.addListener(listener);
|
||||
legacyGameSettings.addListener(listener);
|
||||
}
|
||||
|
||||
/// Helper that stores and dispatches invalidation listeners for this game directory.
|
||||
private final ObservableHelper observableHelper = new ObservableHelper(this);
|
||||
|
||||
/// Adds an invalidation listener.
|
||||
@Override
|
||||
public void addListener(InvalidationListener listener) {
|
||||
observableHelper.addListener(listener);
|
||||
}
|
||||
|
||||
/// Removes an invalidation listener.
|
||||
@Override
|
||||
public void removeListener(InvalidationListener listener) {
|
||||
observableHelper.removeListener(listener);
|
||||
}
|
||||
|
||||
/// Notifies profile observers on the JavaFX thread when the toolkit is available.
|
||||
/// Notifies game directory observers on the JavaFX thread when the toolkit is available.
|
||||
private void invalidate() {
|
||||
try {
|
||||
Platform.runLater(observableHelper::invalidate);
|
||||
@@ -180,12 +161,12 @@ public final class Profile implements Observable {
|
||||
}
|
||||
}
|
||||
|
||||
public record ProfileVersion(Profile profile, @Nullable String version) {
|
||||
}
|
||||
|
||||
public static final class Serializer implements JsonSerializer<Profile>, JsonDeserializer<Profile> {
|
||||
/// Serializes and deserializes game directories.
|
||||
@NotNullByDefault
|
||||
public static final class Serializer implements JsonSerializer<GameDirectory>, JsonDeserializer<GameDirectory> {
|
||||
/// Serializes a game directory to JSON.
|
||||
@Override
|
||||
public JsonElement serialize(@Nullable Profile src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
public JsonElement serialize(@Nullable GameDirectory src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
if (src == null)
|
||||
return JsonNull.INSTANCE;
|
||||
|
||||
@@ -205,8 +186,9 @@ public final class Profile implements Observable {
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
/// Deserializes a game directory from JSON.
|
||||
@Override
|
||||
public @Nullable Profile deserialize(@Nullable JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
public @Nullable GameDirectory deserialize(@Nullable JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
if (!(json instanceof JsonObject obj)) return null;
|
||||
GameDirectoryID id = context.deserialize(obj.get("id"), GameDirectoryID.class);
|
||||
if (id == null) {
|
||||
@@ -216,11 +198,11 @@ public final class Profile implements Observable {
|
||||
}
|
||||
PortablePath path = context.deserialize(obj.get("path"), PortablePath.class);
|
||||
if (path == null) {
|
||||
throw new JsonParseException("Profile path cannot be null");
|
||||
throw new JsonParseException("Game directory path cannot be null");
|
||||
}
|
||||
@Nullable LocalizedText name = context.deserialize(obj.get("name"), LocalizedText.class);
|
||||
|
||||
return new Profile(id,
|
||||
return new GameDirectory(id,
|
||||
name,
|
||||
path,
|
||||
context.deserialize(obj.get("legacyGameSettings"), GameSettingsPresetID.class));
|
||||
@@ -0,0 +1,493 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <[email protected]> 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.setting;
|
||||
|
||||
import javafx.beans.property.*;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.util.PortablePath;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.i18n.LocalizedText;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.UnknownNullability;
|
||||
import org.jetbrains.annotations.UnmodifiableView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.SettingsManager.*;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
/// Manages the merged runtime view of local and user game directories.
|
||||
///
|
||||
/// Local game directories take precedence over user game directories with the same ID. This class is
|
||||
/// the owner of the currently selected game directory and selected instance bookkeeping;
|
||||
/// callers should use this API instead of mutating the game-directory selection fields in
|
||||
/// [LauncherSettings] directly.
|
||||
@NotNullByDefault
|
||||
public final class GameDirectoryManager {
|
||||
|
||||
/// The default current-workspace game directory path.
|
||||
private static final PortablePath CURRENT_GAME_DIRECTORY_PATH = PortablePath.of(".minecraft");
|
||||
|
||||
/// The default user-home game directory path.
|
||||
private static final PortablePath HOME_GAME_DIRECTORY_PATH = PortablePath.fromPath(Metadata.MINECRAFT_DIRECTORY);
|
||||
|
||||
/// Prevents instantiation.
|
||||
private GameDirectoryManager() {
|
||||
}
|
||||
|
||||
/// Creates a game directory ID that does not collide with existing game directories.
|
||||
public static GameDirectoryID newGameDirectoryId() {
|
||||
GameDirectoryID id;
|
||||
do {
|
||||
id = GameDirectoryID.generate();
|
||||
} while (hasGameDirectoryId(id));
|
||||
return id;
|
||||
}
|
||||
|
||||
/// Returns whether an existing game directory uses the given ID.
|
||||
private static boolean hasGameDirectoryId(GameDirectoryID id) {
|
||||
return localGameDirectories().getGameDirectories().stream()
|
||||
.anyMatch(gameDirectory -> gameDirectory.getId().equals(id))
|
||||
|| userGameDirectories().getGameDirectories().stream()
|
||||
.anyMatch(gameDirectory -> gameDirectory.getId().equals(id));
|
||||
}
|
||||
|
||||
/// Returns the display name for the given game directory.
|
||||
public static String getGameDirectoryDisplayName(GameDirectory gameDirectory) {
|
||||
String name = getGameDirectoryCustomName(gameDirectory);
|
||||
if (name != null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
if (isGameDirectoryPath(gameDirectory, CURRENT_GAME_DIRECTORY_PATH)) {
|
||||
return i18n("game_directory.default");
|
||||
}
|
||||
if (isGameDirectoryPath(gameDirectory, HOME_GAME_DIRECTORY_PATH)) {
|
||||
return i18n("game_directory.home");
|
||||
}
|
||||
|
||||
return gameDirectory.getId().toString();
|
||||
}
|
||||
|
||||
/// Returns the custom game directory name in the current locale.
|
||||
public static @Nullable String getGameDirectoryCustomName(GameDirectory gameDirectory) {
|
||||
@Nullable LocalizedText name = gameDirectory.getName();
|
||||
return name != null ? name.getText(I18n.getLocale().getCandidateLocales()) : null;
|
||||
}
|
||||
|
||||
/// Returns whether the game directory uses the given path.
|
||||
private static boolean isGameDirectoryPath(GameDirectory gameDirectory, PortablePath expectedPath) {
|
||||
PortablePath actualPath = gameDirectory.getPath();
|
||||
return actualPath.isAbsolute() == expectedPath.isAbsolute()
|
||||
&& actualPath.getPath().equals(expectedPath.getPath());
|
||||
}
|
||||
|
||||
/// True if [#init()] hasn't been called.
|
||||
private static boolean initialized = false;
|
||||
|
||||
/// The mutable merged game directory list used by UI bindings and game directory selection.
|
||||
private static final ObservableList<GameDirectory> mergedGameDirectories =
|
||||
FXCollections.observableArrayList(gameDirectory -> new javafx.beans.Observable[]{gameDirectory});
|
||||
|
||||
/// Read-only view of [#mergedGameDirectories] exposed to callers.
|
||||
private static final @UnmodifiableView ObservableList<GameDirectory> mergedGameDirectoriesUnmodifiable =
|
||||
FXCollections.unmodifiableObservableList(mergedGameDirectories);
|
||||
|
||||
/// Runtime repositories keyed by game directory object identity.
|
||||
private static final Map<GameDirectory, HMCLGameRepository> repositories = new IdentityHashMap<>();
|
||||
|
||||
/// The selected game directory, or `null` before the fallback game directory is resolved.
|
||||
private static final ObjectProperty<@UnknownNullability GameDirectory> selectedGameDirectory = new SimpleObjectProperty<>(GameDirectoryManager.class, "selectedGameDirectory");
|
||||
|
||||
/// The selected game repository, or `null` before the fallback game directory is resolved.
|
||||
private static final ObjectProperty<@UnknownNullability HMCLGameRepository> selectedRepository = new SimpleObjectProperty<>(GameDirectoryManager.class, "selectedRepository");
|
||||
|
||||
/// The selected instance ID projected from the selected repository.
|
||||
private static final ReadOnlyStringWrapper selectedInstance = new ReadOnlyStringWrapper(GameDirectoryManager.class, "selectedInstance");
|
||||
|
||||
/// Updates [#selectedInstance] when the selected repository changes its selected instance.
|
||||
private static final ChangeListener<String> selectedRepositoryInstanceListener =
|
||||
(observable, oldValue, newValue) -> selectedInstance.set(newValue);
|
||||
|
||||
/// Initializes game directory state from the stores loaded by [SettingsManager].
|
||||
///
|
||||
/// This method creates the built-in local and user-home game directories when required, rebuilds
|
||||
/// the merged game directory view, and restores the selected game directory from [LauncherSettings].
|
||||
public static void init() {
|
||||
if (initialized)
|
||||
throw new IllegalStateException("Already initialized");
|
||||
|
||||
initialized = true;
|
||||
|
||||
rebuildGameDirectories();
|
||||
|
||||
boolean needRebuildGameDirectories = false;
|
||||
if (localGameDirectories().isNewlyCreated() && localGameDirectories().getGameDirectories().isEmpty()) {
|
||||
needRebuildGameDirectories = true;
|
||||
GameDirectories gameDirectories = localGameDirectories();
|
||||
gameDirectories.getGameDirectories().add(new GameDirectory(newGameDirectoryId(), null, CURRENT_GAME_DIRECTORY_PATH));
|
||||
}
|
||||
if (userGameDirectories().isNewlyCreated() && userGameDirectories().getGameDirectories().isEmpty()) {
|
||||
needRebuildGameDirectories = true;
|
||||
GameDirectories gameDirectories = userGameDirectories();
|
||||
gameDirectories.getGameDirectories().add(new GameDirectory(newGameDirectoryId(), null, HOME_GAME_DIRECTORY_PATH));
|
||||
}
|
||||
|
||||
needRebuildGameDirectories |= createDefaultGameDirectoriesIfEmpty();
|
||||
if (needRebuildGameDirectories) {
|
||||
rebuildGameDirectories();
|
||||
}
|
||||
|
||||
assert !mergedGameDirectories.isEmpty();
|
||||
|
||||
@Nullable GameDirectoryID selectedId = settings().selectedGameDirectoryProperty().get();
|
||||
GameDirectory currentGameDirectory = null;
|
||||
|
||||
if (selectedId != null) {
|
||||
for (GameDirectory gameDirectory : mergedGameDirectories) {
|
||||
if (gameDirectory.getId().equals(selectedId)) {
|
||||
currentGameDirectory = gameDirectory;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selectedGameDirectory.addListener((a, b, newValue) -> {
|
||||
if (newValue == null) {
|
||||
throw new IllegalStateException("selectedGameDirectory cannot be null");
|
||||
}
|
||||
|
||||
settings().selectedGameDirectoryProperty().set(newValue.getId());
|
||||
@Nullable HMCLGameRepository oldRepository = selectedRepository.get();
|
||||
if (oldRepository != null) {
|
||||
oldRepository.selectedInstanceProperty().removeListener(selectedRepositoryInstanceListener);
|
||||
}
|
||||
HMCLGameRepository repository = getOrCreateRepository(newValue);
|
||||
selectedRepository.set(repository);
|
||||
selectedInstance.set(repository.getSelectedInstance());
|
||||
repository.selectedInstanceProperty().addListener(selectedRepositoryInstanceListener);
|
||||
repository.refreshVersionsAsync().start();
|
||||
});
|
||||
selectedGameDirectory.set(currentGameDirectory != null ? currentGameDirectory : mergedGameDirectories.get(0));
|
||||
|
||||
EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> {
|
||||
runInFX(() -> {
|
||||
@Nullable HMCLGameRepository repository = selectedRepository.get();
|
||||
if (repository != null && repository == event.getSource()) {
|
||||
repository.refreshSelectedInstance();
|
||||
for (Consumer<HMCLGameRepository> listener : versionsListeners)
|
||||
listener.accept(repository);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Creates the built-in game directories only when no game directory exists.
|
||||
private static boolean createDefaultGameDirectoriesIfEmpty() {
|
||||
if (localGameDirectories().getGameDirectories().isEmpty()
|
||||
&& userGameDirectories().getGameDirectories().isEmpty()) {
|
||||
localGameDirectories().getGameDirectories()
|
||||
.add(new GameDirectory(newGameDirectoryId(), null, CURRENT_GAME_DIRECTORY_PATH));
|
||||
userGameDirectories().getGameDirectories()
|
||||
.add(new GameDirectory(newGameDirectoryId(), null, HOME_GAME_DIRECTORY_PATH));
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Rebuilds the merged runtime game directory view from the two backing stores.
|
||||
private static void rebuildGameDirectories() {
|
||||
GameDirectories userGameDirectories = userGameDirectories();
|
||||
GameDirectories localGameDirectories = localGameDirectories();
|
||||
Map<GameDirectoryID, GameDirectory> visibleGameDirectories = new LinkedHashMap<>();
|
||||
|
||||
for (GameDirectory gameDirectory : localGameDirectories.getGameDirectories()) {
|
||||
GameDirectoryID id = gameDirectory.getId();
|
||||
visibleGameDirectories.put(id, gameDirectory);
|
||||
}
|
||||
for (GameDirectory gameDirectory : userGameDirectories.getGameDirectories()) {
|
||||
GameDirectoryID id = gameDirectory.getId();
|
||||
visibleGameDirectories.putIfAbsent(id, gameDirectory);
|
||||
}
|
||||
|
||||
mergedGameDirectories.setAll(visibleGameDirectories.values());
|
||||
}
|
||||
|
||||
/// Returns the read-only merged game directory list.
|
||||
public static @UnmodifiableView ObservableList<GameDirectory> getGameDirectories() {
|
||||
return mergedGameDirectoriesUnmodifiable;
|
||||
}
|
||||
|
||||
/// Returns the repository for the given game directory, creating it when needed.
|
||||
private static HMCLGameRepository getOrCreateRepository(GameDirectory gameDirectory) {
|
||||
Objects.requireNonNull(gameDirectory);
|
||||
return repositories.computeIfAbsent(gameDirectory, HMCLGameRepository::new);
|
||||
}
|
||||
|
||||
/// Adds a game directory to the per-workspace store.
|
||||
public static void addLocalGameDirectory(GameDirectory gameDirectory) {
|
||||
if (SettingsManager.isLocalGameDirectoriesReadOnly()) {
|
||||
throw new IllegalStateException("Local game directories are read-only");
|
||||
}
|
||||
addGameDirectory(localGameDirectories(), gameDirectory);
|
||||
}
|
||||
|
||||
/// Adds a game directory to the user store.
|
||||
public static void addUserGameDirectory(GameDirectory gameDirectory) {
|
||||
if (SettingsManager.isUserGameDirectoriesReadOnly()) {
|
||||
throw new IllegalStateException("User game directories are read-only");
|
||||
}
|
||||
addGameDirectory(userGameDirectories(), gameDirectory);
|
||||
}
|
||||
|
||||
/// Returns whether an existing game directory can be updated to the given path.
|
||||
///
|
||||
/// @param gameDirectory the game directory to update
|
||||
/// @param path the new game directory path
|
||||
/// @return whether both the current and target stores may be edited
|
||||
public static boolean canUpdateGameDirectory(GameDirectory gameDirectory, PortablePath path) {
|
||||
GameDirectories source = requireGameDirectoryStore(gameDirectory);
|
||||
GameDirectories target = getGameDirectoryStore(path);
|
||||
return !isGameDirectoriesReadOnly(source) && !isGameDirectoriesReadOnly(target);
|
||||
}
|
||||
|
||||
/// Backs up and overwrites read-only game directory files required to update a game directory.
|
||||
///
|
||||
/// @param gameDirectory the game directory to update
|
||||
/// @param path the new game directory path
|
||||
public static void forceOverwriteGameDirectoryFiles(GameDirectory gameDirectory, PortablePath path) {
|
||||
GameDirectories source = requireGameDirectoryStore(gameDirectory);
|
||||
GameDirectories target = getGameDirectoryStore(path);
|
||||
forceOverwriteGameDirectoriesIfReadOnly(source);
|
||||
if (target != source) {
|
||||
forceOverwriteGameDirectoriesIfReadOnly(target);
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates a game directory, moving it between local and user stores when the path type changes.
|
||||
///
|
||||
/// @param gameDirectory the game directory to update
|
||||
/// @param name the new custom game directory name, or `null` for an unnamed game directory
|
||||
/// @param path the new game directory path
|
||||
public static void updateGameDirectory(GameDirectory gameDirectory, @Nullable LocalizedText name, PortablePath path) {
|
||||
GameDirectories source = requireGameDirectoryStore(gameDirectory);
|
||||
GameDirectories target = getGameDirectoryStore(path);
|
||||
if (isGameDirectoriesReadOnly(source) || isGameDirectoriesReadOnly(target)) {
|
||||
throw new IllegalStateException("Game directories are read-only");
|
||||
}
|
||||
|
||||
if (source == target) {
|
||||
gameDirectory.setName(name);
|
||||
gameDirectory.setPath(path);
|
||||
return;
|
||||
}
|
||||
|
||||
source.getGameDirectories().remove(gameDirectory);
|
||||
gameDirectory.setName(name);
|
||||
gameDirectory.setPath(path);
|
||||
addGameDirectory(target, gameDirectory);
|
||||
}
|
||||
|
||||
/// Returns whether the game directory can be removed from all stores containing it.
|
||||
///
|
||||
/// @param gameDirectory the game directory to remove
|
||||
/// @return whether all containing stores may be edited
|
||||
public static boolean canRemoveGameDirectory(GameDirectory gameDirectory) {
|
||||
boolean localContains = localGameDirectories().getGameDirectories().contains(gameDirectory);
|
||||
boolean userContains = userGameDirectories().getGameDirectories().contains(gameDirectory);
|
||||
return (!localContains || !SettingsManager.isLocalGameDirectoriesReadOnly())
|
||||
&& (!userContains || !SettingsManager.isUserGameDirectoriesReadOnly());
|
||||
}
|
||||
|
||||
/// Backs up and overwrites read-only game directory files required to remove a game directory.
|
||||
///
|
||||
/// @param gameDirectory the game directory to remove
|
||||
public static void forceOverwriteGameDirectoryFiles(GameDirectory gameDirectory) {
|
||||
if (localGameDirectories().getGameDirectories().contains(gameDirectory)) {
|
||||
forceOverwriteGameDirectoriesIfReadOnly(localGameDirectories());
|
||||
}
|
||||
if (userGameDirectories().getGameDirectories().contains(gameDirectory)) {
|
||||
forceOverwriteGameDirectoriesIfReadOnly(userGameDirectories());
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a game directory to the given store, replacing a game directory with the same ID in that store.
|
||||
private static void addGameDirectory(GameDirectories gameDirectories, GameDirectory gameDirectory) {
|
||||
Objects.requireNonNull(gameDirectory);
|
||||
ObservableList<GameDirectory> entries = gameDirectories.getGameDirectories();
|
||||
GameDirectoryID id = gameDirectory.getId();
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
if (entries.get(i).getId().equals(id)) {
|
||||
repositories.remove(entries.get(i));
|
||||
settings().setSelectedInstance(id, null);
|
||||
entries.set(i, gameDirectory);
|
||||
rebuildGameDirectories();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
entries.add(gameDirectory);
|
||||
rebuildGameDirectories();
|
||||
}
|
||||
|
||||
/// Removes a game directory and recreates the built-in game directories when the list becomes empty.
|
||||
public static void removeGameDirectory(GameDirectory gameDirectory) {
|
||||
if (!canRemoveGameDirectory(gameDirectory)) {
|
||||
throw new IllegalStateException("Game directories are read-only");
|
||||
}
|
||||
|
||||
userGameDirectories().getGameDirectories().remove(gameDirectory);
|
||||
localGameDirectories().getGameDirectories().remove(gameDirectory);
|
||||
repositories.remove(gameDirectory);
|
||||
createDefaultGameDirectoriesIfEmpty();
|
||||
rebuildGameDirectories();
|
||||
|
||||
if (!mergedGameDirectories.contains(selectedGameDirectory.get())) {
|
||||
setSelectedGameDirectory(mergedGameDirectories.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the store that should own game directories with the given path.
|
||||
private static GameDirectories getGameDirectoryStore(PortablePath path) {
|
||||
Objects.requireNonNull(path);
|
||||
return path.isAbsolute() ? userGameDirectories() : localGameDirectories();
|
||||
}
|
||||
|
||||
/// Returns the store currently containing the game directory.
|
||||
private static @Nullable GameDirectories findGameDirectoryStore(GameDirectory gameDirectory) {
|
||||
Objects.requireNonNull(gameDirectory);
|
||||
if (localGameDirectories().getGameDirectories().contains(gameDirectory)) {
|
||||
return localGameDirectories();
|
||||
}
|
||||
if (userGameDirectories().getGameDirectories().contains(gameDirectory)) {
|
||||
return userGameDirectories();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Returns the store currently containing the game directory, or throws when it is detached.
|
||||
private static GameDirectories requireGameDirectoryStore(GameDirectory gameDirectory) {
|
||||
@Nullable GameDirectories gameDirectories = findGameDirectoryStore(gameDirectory);
|
||||
if (gameDirectories == null) {
|
||||
throw new IllegalArgumentException("Game directory does not belong to a game directory store");
|
||||
}
|
||||
return gameDirectories;
|
||||
}
|
||||
|
||||
/// Returns whether the given game directory store is read-only.
|
||||
private static boolean isGameDirectoriesReadOnly(GameDirectories gameDirectories) {
|
||||
return gameDirectories.isUserFile()
|
||||
? SettingsManager.isUserGameDirectoriesReadOnly()
|
||||
: SettingsManager.isLocalGameDirectoriesReadOnly();
|
||||
}
|
||||
|
||||
/// Backs up and overwrites the given game directory store when it is read-only.
|
||||
private static void forceOverwriteGameDirectoriesIfReadOnly(GameDirectories gameDirectories) {
|
||||
if (!isGameDirectoriesReadOnly(gameDirectories)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (gameDirectories.isUserFile()) {
|
||||
SettingsManager.forceOverwriteUserGameDirectories();
|
||||
} else {
|
||||
SettingsManager.forceOverwriteLocalGameDirectories();
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the selected game directory, creating built-in game directories first if the list is empty.
|
||||
public static GameDirectory getSelectedGameDirectory() {
|
||||
GameDirectory gameDirectory = selectedGameDirectory.get();
|
||||
if (gameDirectory == null) {
|
||||
throw new IllegalStateException("Selected game directory cannot be null");
|
||||
}
|
||||
return gameDirectory;
|
||||
}
|
||||
|
||||
/// Returns the selected game repository.
|
||||
public static HMCLGameRepository getSelectedRepository() {
|
||||
HMCLGameRepository repository = selectedRepository.get();
|
||||
if (repository == null) {
|
||||
throw new IllegalStateException("Selected repository cannot be null");
|
||||
}
|
||||
return repository;
|
||||
}
|
||||
|
||||
/// Sets the selected game directory.
|
||||
public static void setSelectedGameDirectory(GameDirectory gameDirectory) {
|
||||
Objects.requireNonNull(gameDirectory);
|
||||
if (!mergedGameDirectories.contains(gameDirectory)) {
|
||||
throw new IllegalArgumentException("Unknown game directory: " + gameDirectory);
|
||||
}
|
||||
selectedGameDirectory.set(gameDirectory);
|
||||
}
|
||||
|
||||
/// Returns the selected game directory property.
|
||||
///
|
||||
/// The property is exposed for UI binding. Values should be changed through
|
||||
/// [#setSelectedGameDirectory(GameDirectory)] or by bidirectional bindings that only select
|
||||
/// game directories from [#getGameDirectories()].
|
||||
public static ObjectProperty<GameDirectory> selectedGameDirectoryProperty() {
|
||||
return selectedGameDirectory;
|
||||
}
|
||||
|
||||
/// Returns the selected game repository property.
|
||||
public static ObjectProperty<HMCLGameRepository> selectedRepositoryProperty() {
|
||||
return selectedRepository;
|
||||
}
|
||||
|
||||
/// Returns the selected instance property projected from the selected repository.
|
||||
public static ReadOnlyStringProperty selectedInstanceProperty() {
|
||||
return selectedInstance.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
/// Returns the selected instance ID for the selected repository.
|
||||
public static @Nullable String getSelectedInstance() {
|
||||
return getSelectedRepository().getSelectedInstance();
|
||||
}
|
||||
|
||||
/// Sets the selected instance ID for the selected repository.
|
||||
public static void setSelectedInstance(@Nullable String instance) {
|
||||
getSelectedRepository().setSelectedInstance(instance);
|
||||
}
|
||||
|
||||
/// Listeners notified after the selected repository has loaded versions.
|
||||
private static final List<Consumer<HMCLGameRepository>> versionsListeners = new ArrayList<>(4);
|
||||
|
||||
/// Registers a listener that is notified when the selected repository's versions are available.
|
||||
public static void registerVersionsListener(Consumer<HMCLGameRepository> listener) {
|
||||
HMCLGameRepository repository = getSelectedRepository();
|
||||
if (repository.isLoaded())
|
||||
listener.accept(repository);
|
||||
versionsListeners.add(listener);
|
||||
}
|
||||
}
|
||||
@@ -582,15 +582,15 @@ public final class LauncherSettings extends ObservableSetting implements JsonSch
|
||||
|
||||
/// The selected game directory ID.
|
||||
///
|
||||
/// This field is owned by [Profiles]. Code outside [Profiles] should not modify it directly.
|
||||
/// This field is owned by [GameDirectoryManager]. Code outside [GameDirectoryManager] should not modify it directly.
|
||||
@SerializedName(PROPERTY_SELECTED_GAME_DIRECTORY)
|
||||
private final ObjectProperty<@Nullable GameDirectoryID> selectedGameDirectory =
|
||||
new SimpleObjectProperty<>(this, PROPERTY_SELECTED_GAME_DIRECTORY);
|
||||
|
||||
/// Returns the selected game directory ID property.
|
||||
///
|
||||
/// This property is exposed for persistence and [Profiles] integration. Code outside [Profiles]
|
||||
/// should use `Profiles.setSelectedProfile` instead of modifying this property directly.
|
||||
/// This property is exposed for persistence and [GameDirectoryManager] integration. Code outside [GameDirectoryManager]
|
||||
/// should use `GameDirectoryManager.setSelectedGameDirectory` instead of modifying this property directly.
|
||||
public ObjectProperty<@Nullable GameDirectoryID> selectedGameDirectoryProperty() {
|
||||
return selectedGameDirectory;
|
||||
}
|
||||
@@ -607,29 +607,28 @@ public final class LauncherSettings extends ObservableSetting implements JsonSch
|
||||
|
||||
/// Selected instance IDs keyed by game directory ID.
|
||||
///
|
||||
/// This field is owned by [Profiles]. Code outside [Profiles] should not modify it directly.
|
||||
/// This field is owned by [GameDirectoryManager]. Code outside [GameDirectoryManager] should not modify it directly.
|
||||
@SerializedName(PROPERTY_SELECTED_INSTANCE)
|
||||
private final ObservableMap<GameDirectoryID, String> selectedInstance = FXCollections.observableHashMap();
|
||||
|
||||
/// Returns selected instance IDs keyed by game directory ID.
|
||||
///
|
||||
/// This map is exposed for persistence and migration code. Runtime code outside [Profiles] should
|
||||
/// use `Profiles.getSelectedInstance` and `Profiles.setSelectedInstance` instead of mutating it.
|
||||
/// The map stores persisted selected instance values by game directory ID.
|
||||
public ObservableMap<GameDirectoryID, String> getSelectedInstance() {
|
||||
return selectedInstance;
|
||||
}
|
||||
|
||||
/// Returns the selected instance ID for the given game directory ID.
|
||||
///
|
||||
/// This method is intended for [Profiles].
|
||||
@Nullable String getSelectedInstance(@Nullable GameDirectoryID gameDirectoryId) {
|
||||
/// The value is loaded by the game repository for the matching game directory.
|
||||
public @Nullable String getSelectedInstance(@Nullable GameDirectoryID gameDirectoryId) {
|
||||
return gameDirectoryId != null ? selectedInstance.get(gameDirectoryId) : null;
|
||||
}
|
||||
|
||||
/// Sets the selected instance ID for the given game directory ID.
|
||||
///
|
||||
/// This method is intended for [Profiles].
|
||||
void setSelectedInstance(@Nullable GameDirectoryID gameDirectoryId, @Nullable String selectedInstance) {
|
||||
/// Blank values remove the persisted selected instance entry.
|
||||
public void setSelectedInstance(@Nullable GameDirectoryID gameDirectoryId, @Nullable String selectedInstance) {
|
||||
if (gameDirectoryId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1245,7 +1245,7 @@ public final class LegacyConfigMigrator {
|
||||
return changed;
|
||||
}
|
||||
|
||||
/// Migrates profile-global game settings from config files used before HMCL 3.16.
|
||||
/// Migrates global game settings stored under legacy profile entries before HMCL 3.16.
|
||||
static void migrateLegacyPresetSettings(
|
||||
GameDirectories gameDirectories,
|
||||
GameSettingsPresets gameSettingsPresets,
|
||||
@@ -1254,15 +1254,15 @@ public final class LegacyConfigMigrator {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Profile profile : gameDirectories.getGameDirectories()) {
|
||||
@Nullable GameSettingsPresetID legacyGameSettings = profile.getLegacyGameSettings();
|
||||
for (GameDirectory gameDirectory : gameDirectories.getGameDirectories()) {
|
||||
@Nullable GameSettingsPresetID legacyGameSettings = gameDirectory.getLegacyGameSettings();
|
||||
if (legacyGameSettings == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
GameSettings.Preset legacyParent = gameSettingsPresets.getPreset(legacyGameSettings);
|
||||
if (legacyParent == null) {
|
||||
@Nullable String profileName = getLegacyProfileName(profile);
|
||||
@Nullable String profileName = getLegacyProfileName(gameDirectory);
|
||||
if (profileName == null) {
|
||||
continue;
|
||||
}
|
||||
@@ -1279,14 +1279,14 @@ public final class LegacyConfigMigrator {
|
||||
}
|
||||
|
||||
/// Returns the legacy profile name used in `configurations`.
|
||||
private static @Nullable String getLegacyProfileName(Profile profile) {
|
||||
if (LEGACY_DEFAULT_PROFILE_ID.equals(profile.getId())) {
|
||||
private static @Nullable String getLegacyProfileName(GameDirectory gameDirectory) {
|
||||
if (LEGACY_DEFAULT_PROFILE_ID.equals(gameDirectory.getId())) {
|
||||
return LEGACY_DEFAULT_PROFILE;
|
||||
}
|
||||
if (LEGACY_HOME_PROFILE_ID.equals(profile.getId())) {
|
||||
if (LEGACY_HOME_PROFILE_ID.equals(gameDirectory.getId())) {
|
||||
return LEGACY_HOME_PROFILE;
|
||||
}
|
||||
return Profiles.getProfileCustomName(profile);
|
||||
return GameDirectoryManager.getGameDirectoryCustomName(gameDirectory);
|
||||
}
|
||||
|
||||
/// Detached settings migrated out of an old config file.
|
||||
|
||||
@@ -1,477 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <[email protected]> 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.setting;
|
||||
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.util.PortablePath;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.i18n.LocalizedText;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.UnknownNullability;
|
||||
import org.jetbrains.annotations.UnmodifiableView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.SettingsManager.*;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
/// Manages the merged runtime view of local and user game directory profiles.
|
||||
///
|
||||
/// Local profiles take precedence over user profiles with the same ID. This class is
|
||||
/// the only owner of the currently selected profile and selected instance bookkeeping;
|
||||
/// callers should use this API instead of mutating the profile-related fields in
|
||||
/// [LauncherSettings] directly.
|
||||
@NotNullByDefault
|
||||
public final class Profiles {
|
||||
|
||||
/// The default current-workspace game directory path.
|
||||
private static final PortablePath CURRENT_PROFILE_PATH = PortablePath.of(".minecraft");
|
||||
|
||||
/// The default user-home game directory path.
|
||||
private static final PortablePath HOME_PROFILE_PATH = PortablePath.fromPath(Metadata.MINECRAFT_DIRECTORY);
|
||||
|
||||
private Profiles() {
|
||||
}
|
||||
|
||||
/// Creates a profile ID that does not collide with existing profiles.
|
||||
public static GameDirectoryID newProfileId() {
|
||||
GameDirectoryID id;
|
||||
do {
|
||||
id = GameDirectoryID.generate();
|
||||
} while (hasProfileId(id));
|
||||
return id;
|
||||
}
|
||||
|
||||
/// Returns whether an existing profile uses the given ID.
|
||||
private static boolean hasProfileId(GameDirectoryID id) {
|
||||
return localGameDirectories().getGameDirectories().stream()
|
||||
.anyMatch(profile -> profile.getId().equals(id))
|
||||
|| userGameDirectories().getGameDirectories().stream()
|
||||
.anyMatch(profile -> profile.getId().equals(id));
|
||||
}
|
||||
|
||||
/// Returns the display name for the given profile.
|
||||
public static String getProfileDisplayName(Profile profile) {
|
||||
String name = getProfileCustomName(profile);
|
||||
if (name != null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
if (isProfilePath(profile, CURRENT_PROFILE_PATH)) {
|
||||
return i18n("profile.default");
|
||||
}
|
||||
if (isProfilePath(profile, HOME_PROFILE_PATH)) {
|
||||
return i18n("profile.home");
|
||||
}
|
||||
|
||||
return profile.getId().toString();
|
||||
}
|
||||
|
||||
/// Returns the custom profile name in the current locale.
|
||||
public static @Nullable String getProfileCustomName(Profile profile) {
|
||||
@Nullable LocalizedText name = profile.getName();
|
||||
return name != null ? name.getText(I18n.getLocale().getCandidateLocales()) : null;
|
||||
}
|
||||
|
||||
/// Returns whether the profile uses the given path.
|
||||
private static boolean isProfilePath(Profile profile, PortablePath expectedPath) {
|
||||
PortablePath actualPath = profile.getPath();
|
||||
return actualPath.isAbsolute() == expectedPath.isAbsolute()
|
||||
&& actualPath.getPath().equals(expectedPath.getPath());
|
||||
}
|
||||
|
||||
/// True if [#init()] hasn't been called.
|
||||
private static boolean initialized = false;
|
||||
|
||||
private static final ObservableList<Profile> mergedProfiles =
|
||||
FXCollections.observableArrayList(profile -> new javafx.beans.Observable[]{profile});
|
||||
|
||||
private static final @UnmodifiableView ObservableList<Profile> mergedProfilesUnmodifiable =
|
||||
FXCollections.unmodifiableObservableList(mergedProfiles);
|
||||
|
||||
/// The selected profile, or `null` before the fallback profile is resolved.
|
||||
private static final ObjectProperty<@UnknownNullability Profile> selectedProfile = new SimpleObjectProperty<>(Profiles.class, "selectedProfile");
|
||||
|
||||
private static final ReadOnlyStringWrapper selectedInstance = new ReadOnlyStringWrapper(Profiles.class, "selectedInstance");
|
||||
|
||||
/// Initializes profile state from the game directory stores loaded by [SettingsManager].
|
||||
///
|
||||
/// This method creates the built-in local and user-home profiles when required, rebuilds
|
||||
/// the merged profile view, and restores the selected profile from [LauncherSettings].
|
||||
public static void init() {
|
||||
if (initialized)
|
||||
throw new IllegalStateException("Already initialized");
|
||||
|
||||
initialized = true;
|
||||
|
||||
rebuildProfiles();
|
||||
|
||||
boolean needRebuildProfiles = false;
|
||||
if (localGameDirectories().isNewlyCreated() && localGameDirectories().getGameDirectories().isEmpty()) {
|
||||
needRebuildProfiles = true;
|
||||
GameDirectories gameDirectories = localGameDirectories();
|
||||
gameDirectories.getGameDirectories().add(new Profile(newProfileId(), null, CURRENT_PROFILE_PATH));
|
||||
}
|
||||
if (userGameDirectories().isNewlyCreated() && userGameDirectories().getGameDirectories().isEmpty()) {
|
||||
needRebuildProfiles = true;
|
||||
GameDirectories gameDirectories = userGameDirectories();
|
||||
gameDirectories.getGameDirectories().add(new Profile(newProfileId(), null, HOME_PROFILE_PATH));
|
||||
}
|
||||
|
||||
needRebuildProfiles |= createDefaultProfilesIfEmpty();
|
||||
if (needRebuildProfiles) {
|
||||
rebuildProfiles();
|
||||
}
|
||||
|
||||
assert !mergedProfiles.isEmpty();
|
||||
|
||||
@Nullable GameDirectoryID selectedId = settings().selectedGameDirectoryProperty().get();
|
||||
Profile currentProfile = null;
|
||||
|
||||
if (selectedId != null) {
|
||||
for (Profile profile : mergedProfiles) {
|
||||
if (profile.getId().equals(selectedId)) {
|
||||
currentProfile = profile;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selectedProfile.addListener((a, b, newValue) -> {
|
||||
if (newValue == null) {
|
||||
throw new IllegalStateException("selectedProfile cannot be null");
|
||||
}
|
||||
|
||||
settings().selectedGameDirectoryProperty().set(newValue.getId());
|
||||
selectedInstance.set(settings().getSelectedInstance(newValue.getId()));
|
||||
newValue.getRepository().refreshVersionsAsync().start();
|
||||
});
|
||||
selectedProfile.set(currentProfile != null ? currentProfile : mergedProfiles.get(0));
|
||||
|
||||
EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> {
|
||||
runInFX(() -> {
|
||||
@Nullable Profile profile = selectedProfile.get();
|
||||
if (profile != null && profile.getRepository() == event.getSource()) {
|
||||
refreshSelectedVersion(profile);
|
||||
for (Consumer<Profile> listener : versionsListeners)
|
||||
listener.accept(profile);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static void refreshSelectedVersion(Profile profile) {
|
||||
String version = settings().getSelectedInstance(profile.getId());
|
||||
if (!profile.getRepository().hasVersion(version)) {
|
||||
Optional<String> fallback = profile.getRepository().getVersions().stream()
|
||||
.findFirst()
|
||||
.map(Version::getId);
|
||||
version = fallback.orElse(null);
|
||||
if (!Objects.equals(settings().getSelectedInstance(profile.getId()), version)) {
|
||||
settings().setSelectedInstance(profile.getId(), version);
|
||||
}
|
||||
}
|
||||
selectedInstance.set(version);
|
||||
}
|
||||
|
||||
/// Creates the built-in game directories only when no profile exists.
|
||||
private static boolean createDefaultProfilesIfEmpty() {
|
||||
if (localGameDirectories().getGameDirectories().isEmpty()
|
||||
&& userGameDirectories().getGameDirectories().isEmpty()) {
|
||||
localGameDirectories().getGameDirectories()
|
||||
.add(new Profile(newProfileId(), null, CURRENT_PROFILE_PATH));
|
||||
userGameDirectories().getGameDirectories()
|
||||
.add(new Profile(newProfileId(), null, HOME_PROFILE_PATH));
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Rebuilds the merged runtime profile view from the two backing stores.
|
||||
private static void rebuildProfiles() {
|
||||
GameDirectories userGameDirectories = userGameDirectories();
|
||||
GameDirectories localGameDirectories = localGameDirectories();
|
||||
Map<GameDirectoryID, Profile> visibleProfiles = new LinkedHashMap<>();
|
||||
|
||||
for (Profile profile : localGameDirectories.getGameDirectories()) {
|
||||
GameDirectoryID id = profile.getId();
|
||||
visibleProfiles.put(id, profile);
|
||||
}
|
||||
for (Profile profile : userGameDirectories.getGameDirectories()) {
|
||||
GameDirectoryID id = profile.getId();
|
||||
visibleProfiles.putIfAbsent(id, profile);
|
||||
}
|
||||
|
||||
mergedProfiles.setAll(visibleProfiles.values());
|
||||
}
|
||||
|
||||
/// Returns the read-only merged profile list.
|
||||
public static @UnmodifiableView ObservableList<Profile> getProfiles() {
|
||||
return mergedProfilesUnmodifiable;
|
||||
}
|
||||
|
||||
/// Adds a profile to the per-workspace game directory store.
|
||||
public static void addLocalProfile(Profile profile) {
|
||||
if (SettingsManager.isLocalGameDirectoriesReadOnly()) {
|
||||
throw new IllegalStateException("Local game directories are read-only");
|
||||
}
|
||||
addProfile(localGameDirectories(), profile);
|
||||
}
|
||||
|
||||
/// Adds a profile to the user game directory store.
|
||||
public static void addUserProfile(Profile profile) {
|
||||
if (SettingsManager.isUserGameDirectoriesReadOnly()) {
|
||||
throw new IllegalStateException("User game directories are read-only");
|
||||
}
|
||||
addProfile(userGameDirectories(), profile);
|
||||
}
|
||||
|
||||
/// Returns whether an existing profile can be updated to the given path.
|
||||
///
|
||||
/// @param profile the profile to update
|
||||
/// @param path the new profile path
|
||||
/// @return whether both the current and target stores may be edited
|
||||
public static boolean canUpdateProfile(Profile profile, PortablePath path) {
|
||||
GameDirectories source = requireProfileStore(profile);
|
||||
GameDirectories target = getProfileStore(path);
|
||||
return !isGameDirectoriesReadOnly(source) && !isGameDirectoriesReadOnly(target);
|
||||
}
|
||||
|
||||
/// Backs up and overwrites read-only game directory files required to update a profile.
|
||||
///
|
||||
/// @param profile the profile to update
|
||||
/// @param path the new profile path
|
||||
public static void forceOverwriteProfileFiles(Profile profile, PortablePath path) {
|
||||
GameDirectories source = requireProfileStore(profile);
|
||||
GameDirectories target = getProfileStore(path);
|
||||
forceOverwriteGameDirectoriesIfReadOnly(source);
|
||||
if (target != source) {
|
||||
forceOverwriteGameDirectoriesIfReadOnly(target);
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates a profile, moving it between local and user stores when the path type changes.
|
||||
///
|
||||
/// @param profile the profile to update
|
||||
/// @param name the new custom profile name, or `null` for an unnamed profile
|
||||
/// @param path the new profile path
|
||||
public static void updateProfile(Profile profile, @Nullable LocalizedText name, PortablePath path) {
|
||||
GameDirectories source = requireProfileStore(profile);
|
||||
GameDirectories target = getProfileStore(path);
|
||||
if (isGameDirectoriesReadOnly(source) || isGameDirectoriesReadOnly(target)) {
|
||||
throw new IllegalStateException("Game directories are read-only");
|
||||
}
|
||||
|
||||
if (source == target) {
|
||||
profile.setName(name);
|
||||
profile.setPath(path);
|
||||
return;
|
||||
}
|
||||
|
||||
source.getGameDirectories().remove(profile);
|
||||
profile.setName(name);
|
||||
profile.setPath(path);
|
||||
addProfile(target, profile);
|
||||
}
|
||||
|
||||
/// Returns whether the profile can be removed from all stores containing it.
|
||||
///
|
||||
/// @param profile the profile to remove
|
||||
/// @return whether all containing stores may be edited
|
||||
public static boolean canRemoveProfile(Profile profile) {
|
||||
boolean localContains = localGameDirectories().getGameDirectories().contains(profile);
|
||||
boolean userContains = userGameDirectories().getGameDirectories().contains(profile);
|
||||
return (!localContains || !SettingsManager.isLocalGameDirectoriesReadOnly())
|
||||
&& (!userContains || !SettingsManager.isUserGameDirectoriesReadOnly());
|
||||
}
|
||||
|
||||
/// Backs up and overwrites read-only game directory files required to remove a profile.
|
||||
///
|
||||
/// @param profile the profile to remove
|
||||
public static void forceOverwriteProfileFiles(Profile profile) {
|
||||
if (localGameDirectories().getGameDirectories().contains(profile)) {
|
||||
forceOverwriteGameDirectoriesIfReadOnly(localGameDirectories());
|
||||
}
|
||||
if (userGameDirectories().getGameDirectories().contains(profile)) {
|
||||
forceOverwriteGameDirectoriesIfReadOnly(userGameDirectories());
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a profile to the given game directory store, replacing a profile with the same ID in that store.
|
||||
private static void addProfile(GameDirectories gameDirectories, Profile profile) {
|
||||
Objects.requireNonNull(profile);
|
||||
ObservableList<Profile> profiles = gameDirectories.getGameDirectories();
|
||||
GameDirectoryID id = profile.getId();
|
||||
for (int i = 0; i < profiles.size(); i++) {
|
||||
if (profiles.get(i).getId().equals(id)) {
|
||||
profiles.set(i, profile);
|
||||
rebuildProfiles();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
profiles.add(profile);
|
||||
rebuildProfiles();
|
||||
}
|
||||
|
||||
/// Removes a profile and recreates the built-in game directories when the list becomes empty.
|
||||
public static void removeProfile(Profile profile) {
|
||||
if (!canRemoveProfile(profile)) {
|
||||
throw new IllegalStateException("Game directories are read-only");
|
||||
}
|
||||
|
||||
userGameDirectories().getGameDirectories().remove(profile);
|
||||
localGameDirectories().getGameDirectories().remove(profile);
|
||||
createDefaultProfilesIfEmpty();
|
||||
rebuildProfiles();
|
||||
|
||||
if (!mergedProfiles.contains(selectedProfile.get())) {
|
||||
setSelectedProfile(mergedProfiles.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the store that should own profiles with the given path.
|
||||
private static GameDirectories getProfileStore(PortablePath path) {
|
||||
Objects.requireNonNull(path);
|
||||
return path.isAbsolute() ? userGameDirectories() : localGameDirectories();
|
||||
}
|
||||
|
||||
/// Returns the store currently containing the profile.
|
||||
private static @Nullable GameDirectories findProfileStore(Profile profile) {
|
||||
Objects.requireNonNull(profile);
|
||||
if (localGameDirectories().getGameDirectories().contains(profile)) {
|
||||
return localGameDirectories();
|
||||
}
|
||||
if (userGameDirectories().getGameDirectories().contains(profile)) {
|
||||
return userGameDirectories();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Returns the store currently containing the profile, or throws when it is detached.
|
||||
private static GameDirectories requireProfileStore(Profile profile) {
|
||||
@Nullable GameDirectories gameDirectories = findProfileStore(profile);
|
||||
if (gameDirectories == null) {
|
||||
throw new IllegalArgumentException("Profile does not belong to a game directory store");
|
||||
}
|
||||
return gameDirectories;
|
||||
}
|
||||
|
||||
/// Returns whether the given game directory store is read-only.
|
||||
private static boolean isGameDirectoriesReadOnly(GameDirectories gameDirectories) {
|
||||
return gameDirectories.isUserFile()
|
||||
? SettingsManager.isUserGameDirectoriesReadOnly()
|
||||
: SettingsManager.isLocalGameDirectoriesReadOnly();
|
||||
}
|
||||
|
||||
/// Backs up and overwrites the given game directory store when it is read-only.
|
||||
private static void forceOverwriteGameDirectoriesIfReadOnly(GameDirectories gameDirectories) {
|
||||
if (!isGameDirectoriesReadOnly(gameDirectories)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (gameDirectories.isUserFile()) {
|
||||
SettingsManager.forceOverwriteUserGameDirectories();
|
||||
} else {
|
||||
SettingsManager.forceOverwriteLocalGameDirectories();
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the selected profile, creating built-in profiles first if the profile list is empty.
|
||||
public static Profile getSelectedProfile() {
|
||||
Profile profile = selectedProfile.get();
|
||||
if (profile == null) {
|
||||
throw new IllegalStateException("Selected profile cannot be null");
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
|
||||
/// Sets the selected profile.
|
||||
public static void setSelectedProfile(Profile profile) {
|
||||
Objects.requireNonNull(profile);
|
||||
if (!mergedProfiles.contains(profile)) {
|
||||
throw new IllegalArgumentException("Unknown profile: " + profile);
|
||||
}
|
||||
selectedProfile.set(profile);
|
||||
}
|
||||
|
||||
/// Returns the selected profile property.
|
||||
///
|
||||
/// The property is exposed for UI binding. Values should be changed through
|
||||
/// [#setSelectedProfile(Profile)] or by bidirectional bindings that only select
|
||||
/// profiles from [#getProfiles()].
|
||||
public static ObjectProperty<Profile> selectedProfileProperty() {
|
||||
return selectedProfile;
|
||||
}
|
||||
|
||||
/// Returns the selected instance property for the selected profile.
|
||||
public static ReadOnlyStringProperty selectedInstanceProperty() {
|
||||
return selectedInstance.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
/// Returns the selected instance ID for the selected profile.
|
||||
public static @Nullable String getSelectedInstance() {
|
||||
return selectedInstance.get();
|
||||
}
|
||||
|
||||
/// Returns the selected instance ID for the given profile.
|
||||
public static @Nullable String getSelectedInstance(Profile profile) {
|
||||
return settings().getSelectedInstance(profile.getId());
|
||||
}
|
||||
|
||||
/// Sets the selected instance ID for the currently selected profile.
|
||||
public static void setSelectedInstance(@Nullable String instance) {
|
||||
@Nullable Profile profile = selectedProfile.get();
|
||||
if (profile != null) {
|
||||
setSelectedInstance(profile, instance);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the selected instance ID for the given profile.
|
||||
public static void setSelectedInstance(Profile profile, @Nullable String instance) {
|
||||
settings().setSelectedInstance(profile.getId(), instance);
|
||||
if (profile == selectedProfile.get()) {
|
||||
selectedInstance.set(settings().getSelectedInstance(profile.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
private static final List<Consumer<Profile>> versionsListeners = new ArrayList<>(4);
|
||||
|
||||
/// Registers a listener that is notified when the selected profile's versions are available.
|
||||
public static void registerVersionsListener(Consumer<Profile> listener) {
|
||||
Profile profile = getSelectedProfile();
|
||||
if (profile.getRepository().isLoaded())
|
||||
listener.accept(profile);
|
||||
versionsListeners.add(listener);
|
||||
}
|
||||
}
|
||||
@@ -1011,7 +1011,7 @@ public final class SettingsManager {
|
||||
gameSettingsAccess = SettingFileAccess.READ_WRITE;
|
||||
}
|
||||
|
||||
/// Backs up and overwrites local `config/game-directories.json` with the currently loaded profiles.
|
||||
/// Backs up and overwrites local `config/game-directories.json` with the currently loaded game directories.
|
||||
public static void forceOverwriteLocalGameDirectories() {
|
||||
boolean installAutoSave = !localGameDirectories().isSavable();
|
||||
LOCAL_GAME_DIRECTORIES_FILE.backupAndOverwrite(localGameDirectories());
|
||||
@@ -1021,7 +1021,7 @@ public final class SettingsManager {
|
||||
localGameDirectoriesAccess = SettingFileAccess.READ_WRITE;
|
||||
}
|
||||
|
||||
/// Backs up and overwrites user `config/user-game-directories.json` with the currently loaded profiles.
|
||||
/// Backs up and overwrites user `config/user-game-directories.json` with the currently loaded game directories.
|
||||
public static void forceOverwriteUserGameDirectories() {
|
||||
boolean installAutoSave = !userGameDirectories().isSavable();
|
||||
USER_GAME_DIRECTORIES_FILE.backupAndOverwrite(userGameDirectories());
|
||||
|
||||
@@ -702,8 +702,8 @@ public final class Controllers {
|
||||
Controllers.navigate(Controllers.getSettingsPage());
|
||||
break;
|
||||
case "hmcl://game/launch":
|
||||
Profile profile = Profiles.getSelectedProfile();
|
||||
Versions.launch(profile, Profiles.getSelectedInstance(profile), LauncherHelper::setKeep);
|
||||
var repository = GameDirectoryManager.getSelectedRepository();
|
||||
Versions.launch(repository, repository.getSelectedInstance(), LauncherHelper::setKeep);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <[email protected]> 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.directory;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.scene.control.RadioButton;
|
||||
import javafx.scene.control.Skin;
|
||||
|
||||
import org.jackhuang.hmcl.setting.GameDirectory;
|
||||
import org.jackhuang.hmcl.setting.GameDirectoryManager;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
/// Navigation drawer item for one game directory entry.
|
||||
@NotNullByDefault
|
||||
public class GameDirectoryListItem extends RadioButton {
|
||||
/// Game directory represented by this item.
|
||||
private final GameDirectory gameDirectory;
|
||||
|
||||
/// Creates a list item for the given game directory.
|
||||
///
|
||||
/// @param gameDirectory the represented game directory
|
||||
public GameDirectoryListItem(GameDirectory gameDirectory) {
|
||||
this.gameDirectory = gameDirectory;
|
||||
getStyleClass().setAll("game-directory-list-item", "navigation-drawer-item");
|
||||
setUserData(gameDirectory);
|
||||
|
||||
this.selectedProperty().bind(Bindings.equal(gameDirectory, GameDirectoryManager.selectedGameDirectoryProperty()));
|
||||
}
|
||||
|
||||
/// Creates the JavaFX skin for this item.
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new GameDirectoryListItemSkin(this);
|
||||
}
|
||||
|
||||
/// Removes the represented game directory after handling read-only storage.
|
||||
public void remove() {
|
||||
if (!GameDirectoryManager.canRemoveGameDirectory(gameDirectory)) {
|
||||
Controllers.confirmBackupAndOverwrite(i18n("settings.game_directories.read_only"), () -> {
|
||||
GameDirectoryManager.forceOverwriteGameDirectoryFiles(gameDirectory);
|
||||
GameDirectoryManager.removeGameDirectory(gameDirectory);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
GameDirectoryManager.removeGameDirectory(gameDirectory);
|
||||
}
|
||||
|
||||
/// Returns the represented game directory.
|
||||
public GameDirectory getGameDirectory() {
|
||||
return gameDirectory;
|
||||
}
|
||||
|
||||
}
|
||||
+14
-7
@@ -15,7 +15,7 @@
|
||||
* 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.profile;
|
||||
package org.jackhuang.hmcl.ui.directory;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import javafx.css.PseudoClass;
|
||||
@@ -25,16 +25,23 @@ import javafx.scene.Node;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.GameDirectoryManager;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
|
||||
public class ProfileListItemSkin extends SkinBase<ProfileListItem> {
|
||||
/// Skin for [GameDirectoryListItem].
|
||||
@NotNullByDefault
|
||||
public class GameDirectoryListItemSkin extends SkinBase<GameDirectoryListItem> {
|
||||
/// Pseudo class applied to the selected item.
|
||||
private static final PseudoClass SELECTED = PseudoClass.getPseudoClass("selected");
|
||||
|
||||
public ProfileListItemSkin(ProfileListItem skinnable) {
|
||||
/// Creates the skin for a game directory list item.
|
||||
///
|
||||
/// @param skinnable the list item controlled by this skin
|
||||
public GameDirectoryListItemSkin(GameDirectoryListItem skinnable) {
|
||||
super(skinnable);
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
@@ -45,7 +52,7 @@ public class ProfileListItemSkin extends SkinBase<ProfileListItem> {
|
||||
skinnable.pseudoClassStateChanged(SELECTED, active);
|
||||
});
|
||||
|
||||
FXUtils.onClicked(getSkinnable(), () -> Profiles.setSelectedProfile(skinnable.getProfile()));
|
||||
FXUtils.onClicked(getSkinnable(), () -> GameDirectoryManager.setSelectedGameDirectory(skinnable.getGameDirectory()));
|
||||
|
||||
Node left = SVG.FOLDER.createIcon(20);
|
||||
left.setMouseTransparent(true);
|
||||
@@ -67,8 +74,8 @@ public class ProfileListItemSkin extends SkinBase<ProfileListItem> {
|
||||
right.getChildren().add(btnRemove);
|
||||
root.setRight(right);
|
||||
|
||||
item.titleProperty().bind(skinnable.titleProperty());
|
||||
item.subtitleProperty().bind(skinnable.subtitleProperty());
|
||||
item.titleProperty().set(GameDirectoryManager.getGameDirectoryDisplayName(skinnable.getGameDirectory()));
|
||||
item.subtitleProperty().set(skinnable.getGameDirectory().getPath().toString());
|
||||
|
||||
getChildren().setAll(container);
|
||||
}
|
||||
+75
-54
@@ -15,7 +15,7 @@
|
||||
* 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.profile;
|
||||
package org.jackhuang.hmcl.ui.directory;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
@@ -33,8 +33,8 @@ import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.GameDirectory;
|
||||
import org.jackhuang.hmcl.setting.GameDirectoryManager;
|
||||
import org.jackhuang.hmcl.setting.SettingsManager;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
@@ -44,6 +44,7 @@ import org.jackhuang.hmcl.util.PortablePath;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.i18n.LocalizedText;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.nio.file.InvalidPathException;
|
||||
@@ -53,26 +54,39 @@ import java.util.Optional;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class ProfilePage extends BorderPane implements DecoratorPage {
|
||||
/// Page used to create or edit a game directory entry.
|
||||
@NotNullByDefault
|
||||
public final class GameDirectoryPage extends BorderPane implements DecoratorPage {
|
||||
/// Decorator title state for this page.
|
||||
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();
|
||||
|
||||
/// Editable game directory path shown in the directory chooser.
|
||||
private final StringProperty location;
|
||||
private final @Nullable Profile profile;
|
||||
private final JFXTextField txtProfileName;
|
||||
|
||||
/// Game directory being edited, or `null` when creating a new entry.
|
||||
private final @Nullable GameDirectory gameDirectory;
|
||||
|
||||
/// Text field used to edit the game directory display name.
|
||||
private final JFXTextField txtGameDirectoryName;
|
||||
|
||||
/// Directory chooser bound to [#location].
|
||||
private final LineFileChooserButton gameDir;
|
||||
|
||||
/// Toggle that controls whether a saved path is converted to a relative path when possible.
|
||||
private final LineToggleButton toggleUseRelativePath;
|
||||
|
||||
/**
|
||||
* @param profile null if creating a new profile.
|
||||
*/
|
||||
public ProfilePage(@Nullable Profile profile) {
|
||||
/// Creates a page for adding a new game directory or editing an existing game directory.
|
||||
///
|
||||
/// @param gameDirectory the edited game directory, or `null` when creating a new entry
|
||||
public GameDirectoryPage(@Nullable GameDirectory gameDirectory) {
|
||||
getStyleClass().add("gray-background");
|
||||
|
||||
this.profile = profile;
|
||||
String profileDisplayName = Optional.ofNullable(profile).map(Profiles::getProfileDisplayName).orElse("");
|
||||
this.gameDirectory = gameDirectory;
|
||||
String gameDirectoryDisplayName = Optional.ofNullable(gameDirectory).map(GameDirectoryManager::getGameDirectoryDisplayName).orElse("");
|
||||
|
||||
state.set(State.fromTitle(profile == null ? i18n("profile.new") : i18n("profile") + " - " + profileDisplayName));
|
||||
state.set(State.fromTitle(gameDirectory == null ? i18n("game_directory.new") : i18n("game_directory") + " - " + gameDirectoryDisplayName));
|
||||
location = new SimpleStringProperty(this, "location",
|
||||
Optional.ofNullable(profile).map(Profile::getPath).map(PortablePath::toPath).map(FileUtils::getAbsolutePath).orElse(".minecraft"));
|
||||
Optional.ofNullable(gameDirectory).map(GameDirectory::getPath).map(PortablePath::toPath).map(FileUtils::getAbsolutePath).orElse(".minecraft"));
|
||||
|
||||
ScrollPane scroll = new ScrollPane();
|
||||
this.setCenter(scroll);
|
||||
@@ -84,50 +98,50 @@ public final class ProfilePage extends BorderPane implements DecoratorPage {
|
||||
{
|
||||
ComponentList componentList = new ComponentList();
|
||||
{
|
||||
BorderPane profileNamePane = new BorderPane();
|
||||
BorderPane gameDirectoryNamePane = new BorderPane();
|
||||
{
|
||||
Label label = new Label(i18n("profile.name"));
|
||||
profileNamePane.setLeft(label);
|
||||
Label label = new Label(i18n("game_directory.name"));
|
||||
gameDirectoryNamePane.setLeft(label);
|
||||
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
|
||||
|
||||
txtProfileName = new JFXTextField();
|
||||
profileNamePane.setRight(txtProfileName);
|
||||
txtGameDirectoryName = new JFXTextField();
|
||||
gameDirectoryNamePane.setRight(txtGameDirectoryName);
|
||||
RequiredFieldValidator validator = new RequiredFieldValidator();
|
||||
validator.setMessage(i18n("input.not_empty"));
|
||||
txtProfileName.getValidators().add(validator);
|
||||
BorderPane.setMargin(txtProfileName, new Insets(8, 0, 8, 0));
|
||||
txtGameDirectoryName.getValidators().add(validator);
|
||||
BorderPane.setMargin(txtGameDirectoryName, new Insets(8, 0, 8, 0));
|
||||
|
||||
txtProfileName.setText(profileDisplayName);
|
||||
txtProfileName.getValidators().add(new ValidatorBase() {
|
||||
txtGameDirectoryName.setText(gameDirectoryDisplayName);
|
||||
txtGameDirectoryName.getValidators().add(new ValidatorBase() {
|
||||
{
|
||||
setMessage(i18n("profile.already_exists"));
|
||||
setMessage(i18n("game_directory.already_exists"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void eval() {
|
||||
JFXTextField control = (JFXTextField) this.getSrcControl();
|
||||
hasErrors.set(Profiles.getProfiles().stream()
|
||||
.anyMatch(profile -> Objects.equals(
|
||||
Profiles.getProfileCustomName(profile), control.getText())));
|
||||
hasErrors.set(GameDirectoryManager.getGameDirectories().stream()
|
||||
.anyMatch(existingGameDirectory -> Objects.equals(
|
||||
GameDirectoryManager.getGameDirectoryCustomName(existingGameDirectory), control.getText())));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
gameDir = new LineFileChooserButton();
|
||||
gameDir.setTitle(i18n("profile.instance_directory"));
|
||||
gameDir.setFileChooserTitle(i18n("profile.instance_directory.choose"));
|
||||
gameDir.setTitle(i18n("game_directory.instance_directory"));
|
||||
gameDir.setFileChooserTitle(i18n("game_directory.instance_directory.choose"));
|
||||
gameDir.setType(LineFileChooserButton.Type.OPEN_DIRECTORY);
|
||||
gameDir.locationProperty().bindBidirectional(location);
|
||||
|
||||
toggleUseRelativePath = new LineToggleButton();
|
||||
toggleUseRelativePath.setTitle(i18n("profile.use_relative_path"));
|
||||
toggleUseRelativePath.setTitle(i18n("game_directory.use_relative_path"));
|
||||
|
||||
gameDir.convertToRelativePathProperty().bind(toggleUseRelativePath.selectedProperty());
|
||||
if (profile != null) {
|
||||
toggleUseRelativePath.setSelected(!profile.getPath().isAbsolute());
|
||||
if (gameDirectory != null) {
|
||||
toggleUseRelativePath.setSelected(!gameDirectory.getPath().isAbsolute());
|
||||
}
|
||||
|
||||
componentList.getContent().setAll(profileNamePane, gameDir, toggleUseRelativePath);
|
||||
componentList.getContent().setAll(gameDirectoryNamePane, gameDir, toggleUseRelativePath);
|
||||
}
|
||||
|
||||
rootPane.getChildren().setAll(componentList);
|
||||
@@ -149,8 +163,8 @@ public final class ProfilePage extends BorderPane implements DecoratorPage {
|
||||
saveButton.setPrefSize(100, 40);
|
||||
saveButton.setOnAction(e -> onSave());
|
||||
saveButton.disableProperty().bind(Bindings.createBooleanBinding(
|
||||
() -> !txtProfileName.validate() || StringUtils.isBlank(getLocation()),
|
||||
txtProfileName.textProperty(), location));
|
||||
() -> !txtGameDirectoryName.validate() || StringUtils.isBlank(getLocation()),
|
||||
txtGameDirectoryName.textProperty(), location));
|
||||
}
|
||||
|
||||
ChangeListener<String> locationChangeListener = (observable, oldValue, newValue) -> {
|
||||
@@ -170,64 +184,65 @@ public final class ProfilePage extends BorderPane implements DecoratorPage {
|
||||
|
||||
String suggestedName = FileUtils.getName(parent);
|
||||
if (!suggestedName.isBlank()) {
|
||||
txtProfileName.setText(suggestedName);
|
||||
txtGameDirectoryName.setText(suggestedName);
|
||||
}
|
||||
};
|
||||
locationProperty().addListener(locationChangeListener);
|
||||
|
||||
txtProfileName.textProperty().addListener(new ChangeListener<>() {
|
||||
txtGameDirectoryName.textProperty().addListener(new ChangeListener<>() {
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
|
||||
if (txtProfileName.isFocused()) {
|
||||
txtProfileName.textProperty().removeListener(this);
|
||||
if (txtGameDirectoryName.isFocused()) {
|
||||
txtGameDirectoryName.textProperty().removeListener(this);
|
||||
locationProperty().removeListener(locationChangeListener);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Saves the edited game directory or adds a new entry to the appropriate game directory store.
|
||||
private void onSave() {
|
||||
if (profile != null) {
|
||||
LocalizedText name = LocalizedText.plain(txtProfileName.getText());
|
||||
PortablePath path = StringUtils.isNotBlank(getLocation()) ? createPortableLocation() : profile.getPath();
|
||||
if (!Profiles.canUpdateProfile(profile, path)) {
|
||||
if (gameDirectory != null) {
|
||||
LocalizedText name = LocalizedText.plain(txtGameDirectoryName.getText());
|
||||
PortablePath path = StringUtils.isNotBlank(getLocation()) ? createPortableLocation() : gameDirectory.getPath();
|
||||
if (!GameDirectoryManager.canUpdateGameDirectory(gameDirectory, path)) {
|
||||
Controllers.confirmBackupAndOverwrite(i18n("settings.game_directories.read_only"), () -> {
|
||||
Profiles.forceOverwriteProfileFiles(profile, path);
|
||||
Profiles.updateProfile(profile, name, path);
|
||||
GameDirectoryManager.forceOverwriteGameDirectoryFiles(gameDirectory, path);
|
||||
GameDirectoryManager.updateGameDirectory(gameDirectory, name, path);
|
||||
fireEvent(new PageCloseEvent());
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Profiles.updateProfile(profile, name, path);
|
||||
GameDirectoryManager.updateGameDirectory(gameDirectory, name, path);
|
||||
} else {
|
||||
if (StringUtils.isBlank(getLocation())) {
|
||||
gameDir.fire();
|
||||
}
|
||||
Profile newProfile = new Profile(
|
||||
Profiles.newProfileId(),
|
||||
LocalizedText.plain(txtProfileName.getText()),
|
||||
GameDirectory newGameDirectory = new GameDirectory(
|
||||
GameDirectoryManager.newGameDirectoryId(),
|
||||
LocalizedText.plain(txtGameDirectoryName.getText()),
|
||||
createPortableLocation());
|
||||
if (newProfile.getPath().isAbsolute()) {
|
||||
if (newGameDirectory.getPath().isAbsolute()) {
|
||||
if (SettingsManager.isUserGameDirectoriesReadOnly()) {
|
||||
Controllers.confirmBackupAndOverwrite(i18n("settings.game_directories.read_only"), () -> {
|
||||
SettingsManager.forceOverwriteUserGameDirectories();
|
||||
Profiles.addUserProfile(newProfile);
|
||||
GameDirectoryManager.addUserGameDirectory(newGameDirectory);
|
||||
fireEvent(new PageCloseEvent());
|
||||
});
|
||||
return;
|
||||
}
|
||||
Profiles.addUserProfile(newProfile);
|
||||
GameDirectoryManager.addUserGameDirectory(newGameDirectory);
|
||||
} else {
|
||||
if (SettingsManager.isLocalGameDirectoriesReadOnly()) {
|
||||
Controllers.confirmBackupAndOverwrite(i18n("settings.game_directories.read_only"), () -> {
|
||||
SettingsManager.forceOverwriteLocalGameDirectories();
|
||||
Profiles.addLocalProfile(newProfile);
|
||||
GameDirectoryManager.addLocalGameDirectory(newGameDirectory);
|
||||
fireEvent(new PageCloseEvent());
|
||||
});
|
||||
return;
|
||||
}
|
||||
Profiles.addLocalProfile(newProfile);
|
||||
GameDirectoryManager.addLocalGameDirectory(newGameDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,19 +264,25 @@ public final class ProfilePage extends BorderPane implements DecoratorPage {
|
||||
return PortablePath.of(getLocation());
|
||||
}
|
||||
|
||||
/// Returns the decorator title state for this page.
|
||||
@Override
|
||||
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||
return state.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
/// Returns the current directory path text.
|
||||
public String getLocation() {
|
||||
return location.get();
|
||||
}
|
||||
|
||||
/// Returns the editable directory path property.
|
||||
public StringProperty locationProperty() {
|
||||
return location;
|
||||
}
|
||||
|
||||
/// Updates the directory path text.
|
||||
///
|
||||
/// @param location the new directory path text
|
||||
public void setLocation(String location) {
|
||||
this.location.set(location);
|
||||
}
|
||||
@@ -25,9 +25,9 @@ import org.jackhuang.hmcl.addon.RemoteAddon;
|
||||
import org.jackhuang.hmcl.addon.repository.CurseForgeRemoteAddonRepository;
|
||||
import org.jackhuang.hmcl.download.*;
|
||||
import org.jackhuang.hmcl.download.game.GameRemoteVersion;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.setting.DownloadProviders;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.GameDirectoryManager;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
@@ -69,11 +69,11 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
public static final org.jackhuang.hmcl.ui.versions.DownloadPage.DownloadCallback FOR_MOD =
|
||||
(downloadProvider, profile, version, mod, file) -> download(downloadProvider, profile, version, file, "mods");
|
||||
(downloadProvider, repository, version, mod, file) -> download(downloadProvider, repository, version, file, "mods");
|
||||
public static final org.jackhuang.hmcl.ui.versions.DownloadPage.DownloadCallback FOR_RESOURCE_PACK =
|
||||
(downloadProvider, profile, version, pack, file) -> download(downloadProvider, profile, version, file, "resourcepacks");
|
||||
(downloadProvider, repository, version, pack, file) -> download(downloadProvider, repository, version, file, "resourcepacks");
|
||||
public static final org.jackhuang.hmcl.ui.versions.DownloadPage.DownloadCallback FOR_SHADER =
|
||||
(downloadProvider, profile, version, shader, file) -> download(downloadProvider, profile, version, file, "shaderpacks");
|
||||
(downloadProvider, repository, version, shader, file) -> download(downloadProvider, repository, version, file, "shaderpacks");
|
||||
|
||||
private final ReadOnlyObjectWrapper<DecoratorPage.State> state = new ReadOnlyObjectWrapper<>(DecoratorPage.State.fromTitle(i18n("download"), -1));
|
||||
private final TabHeader tab;
|
||||
@@ -96,8 +96,8 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
newGameTab.setNodeSupplier(loadVersionFor(() -> new VersionsPage(versionPageNavigator, i18n("install.installer.choose", i18n("install.installer.game")), "", DownloadProviders.getDownloadProvider(),
|
||||
"game", versionPageNavigator::onGameSelected)));
|
||||
modpackTab.setNodeSupplier(loadVersionFor(() -> {
|
||||
DownloadListPage page = HMCLLocalizedDownloadListPage.ofModPack((downloadProvider, profile, __, modpack, file) -> {
|
||||
Versions.downloadModpackImpl(downloadProvider, profile, uploadVersion, modpack, file);
|
||||
DownloadListPage page = HMCLLocalizedDownloadListPage.ofModPack((downloadProvider, repository, __, modpack, file) -> {
|
||||
Versions.downloadModpackImpl(downloadProvider, repository, uploadVersion, modpack, file);
|
||||
}, false);
|
||||
|
||||
JFXButton installLocalModpackButton = FXUtils.newRaisedButton(i18n("install.modpack"));
|
||||
@@ -112,7 +112,7 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
worldTab.setNodeSupplier(loadVersionFor(() -> new DownloadListPage(CurseForgeRemoteAddonRepository.WORLDS)));
|
||||
tab = new TabHeader(transitionPane, newGameTab, modpackTab, modTab, resourcePackTab, shaderTab, worldTab);
|
||||
|
||||
Profiles.registerVersionsListener(this::loadVersions);
|
||||
GameDirectoryManager.registerVersionsListener(this::loadVersions);
|
||||
|
||||
tab.select(newGameTab);
|
||||
|
||||
@@ -134,17 +134,17 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
private static <T extends Node> Supplier<T> loadVersionFor(Supplier<T> nodeSupplier) {
|
||||
return () -> {
|
||||
T node = nodeSupplier.get();
|
||||
if (node instanceof VersionPage.VersionLoadable) {
|
||||
((VersionPage.VersionLoadable) node).loadVersion(Profiles.getSelectedProfile(), null);
|
||||
if (node instanceof VersionPage.GameInstanceLoadable loadable) {
|
||||
loadable.loadInstance(GameDirectoryManager.getSelectedRepository(), null);
|
||||
}
|
||||
return node;
|
||||
};
|
||||
}
|
||||
|
||||
public static void download(DownloadProvider downloadProvider, Profile profile, @Nullable String version, RemoteAddon.Version file, String subdirectoryName) {
|
||||
if (version == null) version = Profiles.getSelectedInstance(profile);
|
||||
public static void download(DownloadProvider downloadProvider, HMCLGameRepository repository, @Nullable String version, RemoteAddon.Version file, String subdirectoryName) {
|
||||
if (version == null) version = repository.getSelectedInstance();
|
||||
|
||||
Path runDirectory = profile.getRepository().hasVersion(version) ? profile.getRepository().getRunDirectory(version) : profile.getRepository().getBaseDirectory();
|
||||
Path runDirectory = repository.hasVersion(version) ? repository.getRunDirectory(version) : repository.getBaseDirectory();
|
||||
|
||||
Set<String> existingFiles;
|
||||
|
||||
@@ -178,29 +178,29 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
}
|
||||
}), i18n("message.downloading"), TaskCancellationAction.NORMAL);
|
||||
handler.resolve();
|
||||
}, file.file().filename(), new Validator(i18n("install.new_game.malformed"), FileUtils::isNameValid), new Validator(i18n("profile.already_exists"), (it) -> !finalExistingFiles.contains(it)));
|
||||
}, file.file().filename(), new Validator(i18n("install.new_game.malformed"), FileUtils::isNameValid), new Validator(i18n("game_directory.already_exists"), (it) -> !finalExistingFiles.contains(it)));
|
||||
|
||||
}
|
||||
|
||||
private void loadVersions(Profile profile) {
|
||||
private void loadVersions(HMCLGameRepository repository) {
|
||||
listenerHolder = new WeakListenerHolder();
|
||||
runInFX(() -> {
|
||||
if (profile == Profiles.getSelectedProfile()) {
|
||||
listenerHolder.add(FXUtils.onWeakChangeAndOperate(Profiles.selectedInstanceProperty(), version -> {
|
||||
if (repository.getGameDirectory() == GameDirectoryManager.getSelectedGameDirectory()) {
|
||||
listenerHolder.add(FXUtils.onWeakChangeAndOperate(GameDirectoryManager.selectedInstanceProperty(), version -> {
|
||||
if (modTab.isInitialized()) {
|
||||
modTab.getNode().loadVersion(profile, null);
|
||||
modTab.getNode().loadInstance(repository, null);
|
||||
}
|
||||
if (modpackTab.isInitialized()) {
|
||||
modpackTab.getNode().loadVersion(profile, null);
|
||||
modpackTab.getNode().loadInstance(repository, null);
|
||||
}
|
||||
if (resourcePackTab.isInitialized()) {
|
||||
resourcePackTab.getNode().loadVersion(profile, null);
|
||||
resourcePackTab.getNode().loadInstance(repository, null);
|
||||
}
|
||||
if (shaderTab.isInitialized()) {
|
||||
shaderTab.getNode().loadVersion(profile, null);
|
||||
shaderTab.getNode().loadInstance(repository, null);
|
||||
}
|
||||
if (worldTab.isInitialized()) {
|
||||
worldTab.getNode().loadVersion(profile, null);
|
||||
worldTab.getNode().loadInstance(repository, null);
|
||||
}
|
||||
}));
|
||||
}
|
||||
@@ -277,30 +277,31 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
}
|
||||
|
||||
public void onGameSelected() {
|
||||
Profile profile = Profiles.getSelectedProfile();
|
||||
if (profile.getRepository().isLoaded()) {
|
||||
Controllers.getDecorator().startWizard(new VanillaInstallWizardProvider(profile, (GameRemoteVersion) settings.get("game")), i18n("install.new_game"));
|
||||
HMCLGameRepository repository = GameDirectoryManager.getSelectedRepository();
|
||||
if (repository.isLoaded()) {
|
||||
Controllers.getDecorator().startWizard(new VanillaInstallWizardProvider(repository, (GameRemoteVersion) settings.get("game")), i18n("install.new_game"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class VanillaInstallWizardProvider implements WizardProvider {
|
||||
private final Profile profile;
|
||||
private final HMCLGameRepository repository;
|
||||
private final DefaultDependencyManager dependencyManager;
|
||||
private final DownloadProvider downloadProvider;
|
||||
private final GameRemoteVersion gameVersion;
|
||||
|
||||
public VanillaInstallWizardProvider(Profile profile, GameRemoteVersion gameVersion) {
|
||||
this.profile = profile;
|
||||
public VanillaInstallWizardProvider(HMCLGameRepository repository, GameRemoteVersion gameVersion) {
|
||||
this.repository = repository;
|
||||
this.gameVersion = gameVersion;
|
||||
this.downloadProvider = DownloadProviders.getDownloadProvider();
|
||||
this.dependencyManager = profile.getDependency(downloadProvider);
|
||||
this.dependencyManager = repository.getDependency(downloadProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(SettingsMap settings) {
|
||||
settings.put(ModpackPage.PROFILE, profile);
|
||||
settings.put(ModpackPage.GAME_DIRECTORY, repository.getGameDirectory());
|
||||
settings.put(ModpackPage.REPOSITORY, repository);
|
||||
settings.put(LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId(), gameVersion);
|
||||
}
|
||||
|
||||
@@ -318,9 +319,9 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
});
|
||||
|
||||
return builder.buildAsync().whenComplete(any -> {
|
||||
profile.getRepository().refreshVersions();
|
||||
profile.getRepository().applyDefaultIsolationSetting(name);
|
||||
}).thenRunAsync(Schedulers.javafx(), () -> Profiles.setSelectedInstance(profile, name));
|
||||
repository.refreshVersions();
|
||||
repository.applyDefaultIsolationSetting(name);
|
||||
}).thenRunAsync(Schedulers.javafx(), () -> repository.setSelectedInstance(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -336,7 +337,7 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
public Node createPage(WizardController controller, int step, SettingsMap settings) {
|
||||
switch (step) {
|
||||
case 0:
|
||||
return new InstallersPage(controller, profile.getRepository(), ((RemoteVersion) controller.getSettings().get("game")).getGameVersion(), downloadProvider);
|
||||
return new InstallersPage(controller, repository, ((RemoteVersion) controller.getSettings().get("game")).getGameVersion(), downloadProvider);
|
||||
default:
|
||||
throw new IllegalStateException("error step " + step + ", settings: " + settings + ", pages: " + controller.getPages());
|
||||
}
|
||||
|
||||
@@ -25,8 +25,7 @@ import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.ManuallyCreatedModpackException;
|
||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
import org.jackhuang.hmcl.modpack.Modpack;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.GameDirectoryManager;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
@@ -56,7 +55,7 @@ public final class LocalModpackPage extends ModpackPage {
|
||||
public LocalModpackPage(WizardController controller) {
|
||||
super(controller);
|
||||
|
||||
Profile profile = controller.getSettings().get(ModpackPage.PROFILE);
|
||||
HMCLGameRepository repository = controller.getSettings().get(ModpackPage.REPOSITORY);
|
||||
|
||||
String name = controller.getSettings().get(MODPACK_NAME);
|
||||
if (name != null) {
|
||||
@@ -67,15 +66,15 @@ public final class LocalModpackPage extends ModpackPage {
|
||||
if (installAsVersion) {
|
||||
txtModpackName.getValidators().setAll(
|
||||
new RequiredValidator(),
|
||||
new Validator(i18n("install.new_game.already_exists"), str -> !profile.getRepository().versionIdConflicts(str)),
|
||||
new Validator(i18n("install.new_game.already_exists"), str -> !repository.versionIdConflicts(str)),
|
||||
new Validator(i18n("install.new_game.malformed"), HMCLGameRepository::isValidVersionId));
|
||||
} else {
|
||||
txtModpackName.getValidators().setAll(
|
||||
new RequiredValidator(),
|
||||
new Validator(i18n("install.new_game.already_exists"), str -> !ModpackHelper.isExternalGameNameConflicts(str)
|
||||
&& Profiles.getProfiles().stream()
|
||||
&& GameDirectoryManager.getGameDirectories().stream()
|
||||
.noneMatch(existingProfile ->
|
||||
str.equals(Profiles.getProfileCustomName(existingProfile)))),
|
||||
str.equals(GameDirectoryManager.getGameDirectoryCustomName(existingProfile)))),
|
||||
new Validator(i18n("install.new_game.malformed"), HMCLGameRepository::isValidVersionId));
|
||||
}
|
||||
});
|
||||
|
||||
+19
-19
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.ui.download;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.ManuallyCreatedModpackException;
|
||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
import org.jackhuang.hmcl.modpack.MismatchedModpackTypeException;
|
||||
@@ -25,8 +26,6 @@ import org.jackhuang.hmcl.modpack.Modpack;
|
||||
import org.jackhuang.hmcl.modpack.ModpackCompletionException;
|
||||
import org.jackhuang.hmcl.modpack.UnsupportedModpackException;
|
||||
import org.jackhuang.hmcl.modpack.server.ServerModpackManifest;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
@@ -44,26 +43,26 @@ import java.nio.file.Path;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class ModpackInstallWizardProvider implements WizardProvider {
|
||||
private final Profile profile;
|
||||
private final HMCLGameRepository repository;
|
||||
private final Path file;
|
||||
private final String updateVersion;
|
||||
private String iconUrl;
|
||||
private boolean hasSource;
|
||||
|
||||
public ModpackInstallWizardProvider(Profile profile) {
|
||||
this(profile, null, null);
|
||||
public ModpackInstallWizardProvider(HMCLGameRepository repository) {
|
||||
this(repository, null, null);
|
||||
}
|
||||
|
||||
public ModpackInstallWizardProvider(Profile profile, Path modpackFile) {
|
||||
this(profile, modpackFile, null);
|
||||
public ModpackInstallWizardProvider(HMCLGameRepository repository, Path modpackFile) {
|
||||
this(repository, modpackFile, null);
|
||||
}
|
||||
|
||||
public ModpackInstallWizardProvider(Profile profile, String updateVersion) {
|
||||
this(profile, null, updateVersion);
|
||||
public ModpackInstallWizardProvider(HMCLGameRepository repository, String updateVersion) {
|
||||
this(repository, null, updateVersion);
|
||||
}
|
||||
|
||||
public ModpackInstallWizardProvider(Profile profile, Path modpackFile, String updateVersion) {
|
||||
this.profile = profile;
|
||||
public ModpackInstallWizardProvider(HMCLGameRepository repository, Path modpackFile, String updateVersion) {
|
||||
this.repository = repository;
|
||||
this.file = modpackFile;
|
||||
this.updateVersion = updateVersion;
|
||||
}
|
||||
@@ -80,7 +79,8 @@ public final class ModpackInstallWizardProvider implements WizardProvider {
|
||||
settings.put(LocalModpackPage.MODPACK_NAME, updateVersion);
|
||||
if (StringUtils.isNotBlank(iconUrl))
|
||||
settings.put(LocalModpackPage.MODPACK_ICON_URL, iconUrl);
|
||||
settings.put(ModpackPage.PROFILE, profile);
|
||||
settings.put(ModpackPage.GAME_DIRECTORY, repository.getGameDirectory());
|
||||
settings.put(ModpackPage.REPOSITORY, repository);
|
||||
hasSource = settings.containsKey(LocalModpackPage.MODPACK_FILE) || settings.containsKey(RemoteModpackPage.MODPACK_SERVER_MANIFEST);
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ public final class ModpackInstallWizardProvider implements WizardProvider {
|
||||
boolean isManuallyCreated = settings.getOrDefault(LocalModpackPage.MODPACK_MANUALLY_CREATED, false);
|
||||
|
||||
if (isManuallyCreated) {
|
||||
return ModpackHelper.getInstallManuallyCreatedModpackTask(profile, selected, name, charset);
|
||||
return ModpackHelper.getInstallManuallyCreatedModpackTask(selected, name, charset);
|
||||
}
|
||||
|
||||
if ((selected == null && serverModpackManifest == null) || modpack == null || name == null) return null;
|
||||
@@ -106,9 +106,9 @@ public final class ModpackInstallWizardProvider implements WizardProvider {
|
||||
}
|
||||
try {
|
||||
if (serverModpackManifest != null) {
|
||||
return ModpackHelper.getUpdateTask(profile, serverModpackManifest, modpack.getEncoding(), name, ModpackHelper.readModpackConfiguration(profile.getRepository().getModpackConfiguration(name)));
|
||||
return ModpackHelper.getUpdateTask(repository, serverModpackManifest, modpack.getEncoding(), name, ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(name)));
|
||||
} else {
|
||||
return ModpackHelper.getUpdateTask(profile, selected, modpack.getEncoding(), name, ModpackHelper.readModpackConfiguration(profile.getRepository().getModpackConfiguration(name)));
|
||||
return ModpackHelper.getUpdateTask(repository, selected, modpack.getEncoding(), name, ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(name)));
|
||||
}
|
||||
} catch (UnsupportedModpackException | ManuallyCreatedModpackException e) {
|
||||
Controllers.dialog(i18n("modpack.unsupported"), i18n("message.error"), MessageType.ERROR);
|
||||
@@ -120,11 +120,11 @@ public final class ModpackInstallWizardProvider implements WizardProvider {
|
||||
return null;
|
||||
} else {
|
||||
if (serverModpackManifest != null) {
|
||||
return ModpackHelper.getInstallTask(profile, serverModpackManifest, name, modpack)
|
||||
.thenRunAsync(Schedulers.javafx(), () -> Profiles.setSelectedInstance(profile, name));
|
||||
return ModpackHelper.getInstallTask(repository, serverModpackManifest, name, modpack)
|
||||
.thenRunAsync(Schedulers.javafx(), () -> repository.setSelectedInstance(name));
|
||||
} else {
|
||||
return ModpackHelper.getInstallTask(profile, selected, name, modpack, iconUrl)
|
||||
.thenRunAsync(Schedulers.javafx(), () -> Profiles.setSelectedInstance(profile, name));
|
||||
return ModpackHelper.getInstallTask(repository, selected, name, modpack, iconUrl)
|
||||
.thenRunAsync(Schedulers.javafx(), () -> repository.setSelectedInstance(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,8 @@ import javafx.beans.property.StringProperty;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.setting.GameDirectory;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentList;
|
||||
import org.jackhuang.hmcl.ui.construct.LinePane;
|
||||
@@ -37,7 +38,8 @@ import static javafx.beans.binding.Bindings.createBooleanBinding;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public abstract class ModpackPage extends SpinnerPane implements WizardPage {
|
||||
public static final SettingsMap.Key<Profile> PROFILE = new SettingsMap.Key<>("PROFILE");
|
||||
public static final SettingsMap.Key<GameDirectory> GAME_DIRECTORY = new SettingsMap.Key<>("GAME_DIRECTORY");
|
||||
public static final SettingsMap.Key<HMCLGameRepository> REPOSITORY = new SettingsMap.Key<>("REPOSITORY");
|
||||
|
||||
protected final WizardController controller;
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ import javafx.application.Platform;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.modpack.Modpack;
|
||||
import org.jackhuang.hmcl.modpack.server.ServerModpackManifest;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.WebPage;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
@@ -57,7 +56,7 @@ public final class RemoteModpackPage extends ModpackPage {
|
||||
versionProperty.set(manifest.getVersion());
|
||||
authorProperty.set(manifest.getAuthor());
|
||||
|
||||
Profile profile = controller.getSettings().get(ModpackPage.PROFILE);
|
||||
HMCLGameRepository repository = controller.getSettings().get(ModpackPage.REPOSITORY);
|
||||
String name = controller.getSettings().get(MODPACK_NAME);
|
||||
if (name != null) {
|
||||
txtModpackName.setText(name);
|
||||
@@ -67,7 +66,7 @@ public final class RemoteModpackPage extends ModpackPage {
|
||||
txtModpackName.setText(manifest.getName().trim());
|
||||
txtModpackName.getValidators().addAll(
|
||||
new RequiredValidator(),
|
||||
new Validator(i18n("install.new_game.already_exists"), str -> !profile.getRepository().versionIdConflicts(str)),
|
||||
new Validator(i18n("install.new_game.already_exists"), str -> !repository.versionIdConflicts(str)),
|
||||
new Validator(i18n("install.new_game.malformed"), HMCLGameRepository::isValidVersionId));
|
||||
}
|
||||
|
||||
|
||||
+7
-7
@@ -21,9 +21,9 @@ import javafx.scene.Node;
|
||||
import org.jackhuang.hmcl.download.*;
|
||||
import org.jackhuang.hmcl.download.game.GameAssetIndexDownloadTask;
|
||||
import org.jackhuang.hmcl.download.game.LibraryDownloadException;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.DownloadProviders;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.DownloadException;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
@@ -46,7 +46,7 @@ import java.util.zip.ZipException;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class UpdateInstallerWizardProvider implements WizardProvider {
|
||||
private final Profile profile;
|
||||
private final HMCLGameRepository repository;
|
||||
private final DefaultDependencyManager dependencyManager;
|
||||
private final String gameVersion;
|
||||
private final Version version;
|
||||
@@ -54,14 +54,14 @@ public final class UpdateInstallerWizardProvider implements WizardProvider {
|
||||
private final String oldLibraryVersion;
|
||||
private final DownloadProvider downloadProvider;
|
||||
|
||||
public UpdateInstallerWizardProvider(@NotNull Profile profile, @NotNull String gameVersion, @NotNull Version version, @NotNull String libraryId, @Nullable String oldLibraryVersion) {
|
||||
this.profile = profile;
|
||||
public UpdateInstallerWizardProvider(@NotNull HMCLGameRepository repository, @NotNull String gameVersion, @NotNull Version version, @NotNull String libraryId, @Nullable String oldLibraryVersion) {
|
||||
this.repository = repository;
|
||||
this.gameVersion = gameVersion;
|
||||
this.version = version;
|
||||
this.libraryId = libraryId;
|
||||
this.oldLibraryVersion = oldLibraryVersion;
|
||||
this.downloadProvider = DownloadProviders.getDownloadProvider();
|
||||
this.dependencyManager = profile.getDependency(downloadProvider);
|
||||
this.dependencyManager = repository.getDependency(downloadProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -91,7 +91,7 @@ public final class UpdateInstallerWizardProvider implements WizardProvider {
|
||||
}
|
||||
}
|
||||
|
||||
return ret.thenComposeAsync(profile.getRepository()::saveAsync).thenComposeAsync(profile.getRepository().refreshVersionsAsync()).withStagesHints(hints);
|
||||
return ret.thenComposeAsync(repository::saveAsync).thenComposeAsync(repository.refreshVersionsAsync()).withStagesHints(hints);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -103,7 +103,7 @@ public final class UpdateInstallerWizardProvider implements WizardProvider {
|
||||
controller.onFinish();
|
||||
} else if ("game".equals(libraryId)) {
|
||||
String newGameVersion = ((RemoteVersion) settings.get(libraryId)).getSelfVersion();
|
||||
controller.onNext(new AdditionalInstallersPage(newGameVersion, version, controller, profile.getRepository(), downloadProvider));
|
||||
controller.onNext(new AdditionalInstallersPage(newGameVersion, version, controller, repository, downloadProvider));
|
||||
} else {
|
||||
Controllers.confirm(i18n("install.change_version.confirm", i18n("install.installer." + libraryId), oldLibraryVersion, ((RemoteVersion) settings.get(libraryId)).getSelfVersion()),
|
||||
i18n("install.change_version"), controller::onFinish, controller::onCancel);
|
||||
|
||||
+11
-11
@@ -22,9 +22,8 @@ import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.GameBuilder;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.setting.DownloadProviders;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
@@ -34,19 +33,20 @@ import org.jackhuang.hmcl.util.SettingsMap;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class VanillaInstallWizardProvider implements WizardProvider {
|
||||
private final Profile profile;
|
||||
private final HMCLGameRepository repository;
|
||||
private final DefaultDependencyManager dependencyManager;
|
||||
private final DownloadProvider downloadProvider;
|
||||
|
||||
public VanillaInstallWizardProvider(Profile profile) {
|
||||
this.profile = profile;
|
||||
public VanillaInstallWizardProvider(HMCLGameRepository repository) {
|
||||
this.repository = repository;
|
||||
this.downloadProvider = DownloadProviders.getDownloadProvider();
|
||||
this.dependencyManager = profile.getDependency(downloadProvider);
|
||||
this.dependencyManager = repository.getDependency(downloadProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(SettingsMap settings) {
|
||||
settings.put(ModpackPage.PROFILE, profile);
|
||||
settings.put(ModpackPage.GAME_DIRECTORY, repository.getGameDirectory());
|
||||
settings.put(ModpackPage.REPOSITORY, repository);
|
||||
}
|
||||
|
||||
private Task<Void> finishVersionDownloadingAsync(SettingsMap settings) {
|
||||
@@ -62,10 +62,10 @@ public final class VanillaInstallWizardProvider implements WizardProvider {
|
||||
});
|
||||
|
||||
return builder.buildAsync().whenComplete(any -> {
|
||||
profile.getRepository().refreshVersions();
|
||||
profile.getRepository().applyDefaultIsolationSetting(name);
|
||||
repository.refreshVersions();
|
||||
repository.applyDefaultIsolationSetting(name);
|
||||
})
|
||||
.thenRunAsync(Schedulers.javafx(), () -> Profiles.setSelectedInstance(profile, name));
|
||||
.thenRunAsync(Schedulers.javafx(), () -> repository.setSelectedInstance(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -82,7 +82,7 @@ public final class VanillaInstallWizardProvider implements WizardProvider {
|
||||
switch (step) {
|
||||
case 0:
|
||||
return new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.game")), "", downloadProvider, "game",
|
||||
() -> controller.onNext(new InstallersPage(controller, profile.getRepository(), ((RemoteVersion) controller.getSettings().get("game")).getGameVersion(), downloadProvider)));
|
||||
() -> controller.onNext(new InstallersPage(controller, repository, ((RemoteVersion) controller.getSettings().get("game")).getGameVersion(), downloadProvider)));
|
||||
default:
|
||||
throw new IllegalStateException("error step " + step + ", settings: " + settings + ", pages: " + controller.getPages());
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.ui.export;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.modpack.ModAdviser;
|
||||
import org.jackhuang.hmcl.modpack.ModpackExportInfo;
|
||||
import org.jackhuang.hmcl.modpack.mcbbs.McbbsModpackExportTask;
|
||||
@@ -45,11 +46,11 @@ import java.util.List;
|
||||
import static org.jackhuang.hmcl.setting.SettingsManager.settings;
|
||||
|
||||
public final class ExportWizardProvider implements WizardProvider {
|
||||
private final Profile profile;
|
||||
private final HMCLGameRepository repository;
|
||||
private final String version;
|
||||
|
||||
public ExportWizardProvider(Profile profile, String version) {
|
||||
this.profile = profile;
|
||||
public ExportWizardProvider(HMCLGameRepository repository, String version) {
|
||||
this.repository = repository;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@@ -163,7 +164,7 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
dependency = new McbbsModpackExportTask(profile.getRepository(), version, exportInfo, modpackFile);
|
||||
dependency = new McbbsModpackExportTask(repository, version, exportInfo, modpackFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -183,8 +184,8 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
GameSettings.Effective setting = profile.getRepository().getEffectiveGameSettings(version);
|
||||
dependency = new MultiMCModpackExportTask(profile.getRepository(), version, exportInfo.getWhitelist(),
|
||||
GameSettings.Effective setting = repository.getEffectiveGameSettings(version);
|
||||
dependency = new MultiMCModpackExportTask(repository, version, exportInfo.getWhitelist(),
|
||||
new MultiMCInstanceConfiguration(
|
||||
"OneSix",
|
||||
exportInfo.getName() + "-" + exportInfo.getVersion(),
|
||||
@@ -231,7 +232,7 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
dependency = new ServerModpackExportTask(profile.getRepository(), version, exportInfo, modpackFile);
|
||||
dependency = new ServerModpackExportTask(repository, version, exportInfo, modpackFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -252,7 +253,7 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
@Override
|
||||
public void execute() {
|
||||
dependency = new ModrinthModpackExportTask(
|
||||
profile.getRepository(),
|
||||
repository,
|
||||
version,
|
||||
exportInfo,
|
||||
modpackFile
|
||||
@@ -270,8 +271,8 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
public Node createPage(WizardController controller, int step, SettingsMap settings) {
|
||||
return switch (step) {
|
||||
case 0 -> new ModpackTypeSelectionPage(controller);
|
||||
case 1 -> new ModpackInfoPage(controller, profile.getRepository(), version);
|
||||
case 2 -> new ModpackFileSelectionPage(controller, profile, version, ModAdviser::suggestMod);
|
||||
case 1 -> new ModpackInfoPage(controller, repository, version);
|
||||
case 2 -> new ModpackFileSelectionPage(controller, repository, version, ModAdviser::suggestMod);
|
||||
default -> throw new IllegalArgumentException("step");
|
||||
};
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TreeItem;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.modpack.ModAdviser;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.NoneMultipleSelectionModel;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
@@ -59,13 +59,13 @@ public final class ModpackFileSelectionPage extends BorderPane implements Wizard
|
||||
private final ModAdviser adviser;
|
||||
private final CheckBoxTreeItem<String> rootNode;
|
||||
|
||||
public ModpackFileSelectionPage(WizardController controller, Profile profile, String version, ModAdviser adviser) {
|
||||
public ModpackFileSelectionPage(WizardController controller, HMCLGameRepository repository, String version, ModAdviser adviser) {
|
||||
this.controller = controller;
|
||||
this.version = version;
|
||||
this.adviser = adviser;
|
||||
|
||||
JFXTreeView<String> treeView = new JFXTreeView<>();
|
||||
rootNode = getTreeItem(profile.getRepository().getRunDirectory(version), "minecraft");
|
||||
rootNode = getTreeItem(repository.getRunDirectory(version), "minecraft");
|
||||
treeView.setRoot(rootNode);
|
||||
treeView.setSelectionModel(new NoneMultipleSelectionModel<>());
|
||||
onEscPressed(treeView, () -> controller.onPrev(true));
|
||||
|
||||
@@ -51,7 +51,6 @@ import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.ui.versions.VersionIconDialog;
|
||||
import org.jackhuang.hmcl.ui.versions.VersionPage;
|
||||
import org.jackhuang.hmcl.ui.versions.Versions;
|
||||
import org.jackhuang.hmcl.util.Holder;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.ServerAddress;
|
||||
@@ -78,7 +77,7 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
/// @author Glavo
|
||||
@NotNullByDefault
|
||||
public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
implements DecoratorPage, VersionPage.VersionLoadable, PageAware {
|
||||
implements DecoratorPage, VersionPage.GameInstanceLoadable, PageAware {
|
||||
|
||||
private static final Object INHERIT_BUTTON_TOOLTIP_KEY = new Object();
|
||||
private static final PseudoClass PSEUDO_OVERRIDDEN = PseudoClass.getPseudoClass("overridden");
|
||||
@@ -90,8 +89,11 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
private final ObjectProperty<State> state = new SimpleObjectProperty<>(this, "state", new State("", null, false, false, false));
|
||||
private final WeakListenerHolder holder = new WeakListenerHolder();
|
||||
|
||||
/// The selected profile.
|
||||
private @Nullable Profile profile;
|
||||
/// The selected game directory.
|
||||
private @Nullable GameDirectory gameDirectory;
|
||||
|
||||
/// The selected repository.
|
||||
private @Nullable HMCLGameRepository repository;
|
||||
|
||||
/// The current instance ID.
|
||||
private @Nullable String instanceId;
|
||||
@@ -844,7 +846,7 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
});
|
||||
}
|
||||
|
||||
/// Refreshes parent preset display text because the implicit parent depends on the current profile.
|
||||
/// Refreshes parent preset display text because the implicit parent depends on the current game directory.
|
||||
private void refreshInstanceParentSettingConverter(LineSelectButton<GameSettings.@Nullable Preset> button) {
|
||||
button.setConverter(setting -> setting != null
|
||||
? PresetManagementPane.getPresetDisplayName(setting)
|
||||
@@ -853,19 +855,19 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
|
||||
/// Returns the label for the implicit parent preset selected by a null instance parent.
|
||||
private String getImplicitParentGameSettingsDisplayName() {
|
||||
GameSettings.Preset legacyParent = getProfileLegacyGameSettings();
|
||||
GameSettings.Preset legacyParent = getGameDirectoryLegacyGameSettings();
|
||||
return legacyParent != null
|
||||
? PresetManagementPane.getPresetDisplayName(legacyParent)
|
||||
: i18n("settings.type.global.preset.default");
|
||||
}
|
||||
|
||||
/// Returns the migrated profile-level parent preset, or null when this profile uses the default preset.
|
||||
private @Nullable GameSettings.Preset getProfileLegacyGameSettings() {
|
||||
if (profile == null || profile.getLegacyGameSettings() == null) {
|
||||
/// Returns the migrated game-directory-level parent preset, or `null` when this game directory uses the default preset.
|
||||
private @Nullable GameSettings.Preset getGameDirectoryLegacyGameSettings() {
|
||||
if (gameDirectory == null || gameDirectory.getLegacyGameSettings() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return SettingsManager.getGameSettings(profile.getLegacyGameSettings());
|
||||
return SettingsManager.getGameSettings(gameDirectory.getLegacyGameSettings());
|
||||
}
|
||||
|
||||
/// Adds the title-line inheritance button for the Java selection sublist.
|
||||
@@ -1778,16 +1780,16 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
}
|
||||
|
||||
private boolean isCurrentInstanceModpack() {
|
||||
return profile != null && instanceId != null && profile.getRepository().isModpack(instanceId);
|
||||
return repository != null && instanceId != null && repository.isModpack(instanceId);
|
||||
}
|
||||
|
||||
/// Returns the current instance version root displayed for modpack running directories.
|
||||
private String getCurrentInstanceVersionRoot() {
|
||||
if (profile == null || instanceId == null) {
|
||||
if (repository == null || instanceId == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return profile.getRepository().getVersionRoot(instanceId).toString();
|
||||
return repository.getVersionRoot(instanceId).toString();
|
||||
}
|
||||
|
||||
/// Keeps a listener attached to the current instance's parent preset property.
|
||||
@@ -2385,10 +2387,10 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
return getEffectiveParentGameSettings(instance);
|
||||
}
|
||||
|
||||
/// Returns the runtime parent preset for an instance, including profile-level legacy migration fallback.
|
||||
/// Returns the runtime parent preset for an instance, including the game directory's migrated preset fallback.
|
||||
private GameSettings.Preset getEffectiveParentGameSettings(GameSettings.Instance instance) {
|
||||
if (profile != null) {
|
||||
return profile.getRepository().getParentGameSettings(instance);
|
||||
if (repository != null) {
|
||||
return repository.getParentGameSettings(instance);
|
||||
}
|
||||
|
||||
return getExplicitParentGameSettings(instance);
|
||||
@@ -2428,14 +2430,14 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void loadVersion(Profile profile, @Nullable String instanceId) {
|
||||
this.profile = profile;
|
||||
public void loadInstance(HMCLGameRepository repository, @Nullable String instanceId) {
|
||||
this.gameDirectory = repository.getGameDirectory();
|
||||
this.repository = repository;
|
||||
this.instanceId = instanceId;
|
||||
|
||||
assert isPresetSetting == (instanceId == null);
|
||||
|
||||
if (instanceId != null) {
|
||||
HMCLGameRepository repository = profile.getRepository();
|
||||
@Nullable GameSettings.Instance setting = repository.getInstanceGameSettingsOrCreate(instanceId);
|
||||
this.currentSetting.set((S) setting);
|
||||
setSettingsReadOnly(
|
||||
@@ -2491,12 +2493,12 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
|
||||
/// Backs up and overwrites the current instance's `instance-game-settings.json`.
|
||||
private void forceOverwriteInstanceGameSettings() {
|
||||
if (profile == null || instanceId == null) {
|
||||
if (repository == null || instanceId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Controllers.confirmBackupAndOverwrite(i18n("settings.game.instance_settings.unsupported"), () -> {
|
||||
profile.getRepository().forceOverwriteInstanceGameSettings(instanceId);
|
||||
repository.forceOverwriteInstanceGameSettings(instanceId);
|
||||
setSettingsReadOnly(false, "");
|
||||
});
|
||||
}
|
||||
@@ -2510,10 +2512,10 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
}
|
||||
|
||||
private void loadIcon() {
|
||||
if (profile == null || instanceId == null)
|
||||
if (repository == null || instanceId == null)
|
||||
return;
|
||||
|
||||
iconPickerItem.setImage(profile.getRepository().getVersionIconImage(instanceId));
|
||||
iconPickerItem.setImage(repository.getVersionIconImage(instanceId));
|
||||
}
|
||||
|
||||
/// Refreshes Java selection controls and keeps inherited parent Java properties observed.
|
||||
@@ -2577,13 +2579,12 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
private void initJavaSubtitle() {
|
||||
S setting = currentSetting.get();
|
||||
|
||||
if (setting == null || profile == null)
|
||||
if (setting == null || gameDirectory == null)
|
||||
return;
|
||||
initializeSelectedJava();
|
||||
|
||||
HMCLGameRepository repository = this.profile.getRepository();
|
||||
JavaVersionType javaVersionType = setting.javaTypeProperty().getValue();
|
||||
GameSettings.Effective effectiveSetting = this.instanceId != null ? repository.getEffectiveGameSettings(this.instanceId) : null;
|
||||
GameSettings.Effective effectiveSetting = this.instanceId != null && repository != null ? repository.getEffectiveGameSettings(this.instanceId) : null;
|
||||
JavaVersionType effectiveJavaVersionType = effectiveSetting != null ? effectiveSetting.getInheritable(GameSettings::javaTypeProperty) : javaVersionType;
|
||||
boolean autoSelected = effectiveJavaVersionType == JavaVersionType.AUTO || effectiveJavaVersionType == JavaVersionType.VERSION;
|
||||
|
||||
@@ -2605,8 +2606,8 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
gameVersionNumber = GameVersionNumber.unknown();
|
||||
version = null;
|
||||
} else {
|
||||
gameVersionNumber = GameVersionNumber.asGameVersion(repository.getGameVersion(this.instanceId));
|
||||
version = repository.getResolvedVersion(this.instanceId);
|
||||
gameVersionNumber = repository != null ? GameVersionNumber.asGameVersion(repository.getGameVersion(this.instanceId)) : GameVersionNumber.unknown();
|
||||
version = repository != null ? repository.getResolvedVersion(this.instanceId) : null;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -2626,24 +2627,19 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
|
||||
javaSublist.setDescription("");
|
||||
}
|
||||
|
||||
private void editSpecificSettings() {
|
||||
if (profile != null)
|
||||
Versions.modifyGameSettings(profile, Profiles.getSelectedInstance(profile));
|
||||
}
|
||||
|
||||
private void onExploreIcon() {
|
||||
if (profile == null || instanceId == null)
|
||||
if (repository == null || instanceId == null)
|
||||
return;
|
||||
|
||||
Controllers.dialog(new VersionIconDialog(profile, instanceId, this::loadIcon));
|
||||
Controllers.dialog(new VersionIconDialog(repository, instanceId, this::loadIcon));
|
||||
}
|
||||
|
||||
private void onDeleteIcon() {
|
||||
if (profile == null || instanceId == null)
|
||||
if (repository == null || instanceId == null)
|
||||
return;
|
||||
|
||||
profile.getRepository().deleteIconFile(instanceId);
|
||||
GameSettings.Instance localGameSettings = profile.getRepository().getInstanceGameSettingsOrCreate(instanceId);
|
||||
repository.deleteIconFile(instanceId);
|
||||
GameSettings.Instance localGameSettings = repository.getInstanceGameSettingsOrCreate(instanceId);
|
||||
if (localGameSettings != null) {
|
||||
localGameSettings.iconProperty().setValue(VersionIconType.DEFAULT);
|
||||
}
|
||||
|
||||
@@ -19,9 +19,9 @@ package org.jackhuang.hmcl.ui.main;
|
||||
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.setting.GameSettings;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.GameDirectoryManager;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
@@ -59,7 +59,7 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
|
||||
tab = new TabHeader(transitionPane, gameTab, javaManagementTab, settingsTab, personalizationTab, downloadTab, helpTab, feedbackTab, aboutTab);
|
||||
|
||||
tab.select(gameTab);
|
||||
addEventHandler(Navigator.NavigationEvent.NAVIGATED, event -> gameTab.getNode().loadVersion(Profiles.getSelectedProfile(), null));
|
||||
addEventHandler(Navigator.NavigationEvent.NAVIGATED, event -> gameTab.getNode().loadInstance(GameDirectoryManager.getSelectedRepository(), null));
|
||||
|
||||
AdvancedListBox sideBar = new AdvancedListBox()
|
||||
.addNavigationDrawerTab(tab, gameTab, i18n("settings.type.global.manage"), SVG.STADIA_CONTROLLER, SVG.STADIA_CONTROLLER_FILL)
|
||||
@@ -88,8 +88,8 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
|
||||
tab.onPageHidden();
|
||||
}
|
||||
|
||||
public void showGameSettings(Profile profile) {
|
||||
gameTab.getNode().loadVersion(profile, null);
|
||||
public void showGameSettings(HMCLGameRepository repository) {
|
||||
gameTab.getNode().loadInstance(repository, null);
|
||||
tab.select(gameTab, false);
|
||||
}
|
||||
|
||||
|
||||
@@ -44,10 +44,11 @@ import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.VersionList;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.DownloadProviders;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.GameDirectory;
|
||||
import org.jackhuang.hmcl.setting.GameDirectoryManager;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.theme.Themes;
|
||||
@@ -94,7 +95,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
private final BooleanProperty showUpdateDialog = new SimpleBooleanProperty(this, "showUpdateDialog");
|
||||
private final ObjectProperty<RemoteVersion> latestVersion = new SimpleObjectProperty<>(this, "latestVersion");
|
||||
private final ObservableList<Version> versions = FXCollections.observableArrayList();
|
||||
private Profile profile;
|
||||
private HMCLGameRepository repository;
|
||||
|
||||
private TransitionPane announcementPane;
|
||||
private final StackPane updatePane;
|
||||
@@ -209,7 +210,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
FXUtils.onScroll(launchPane, versions, list -> {
|
||||
String currentId = getCurrentGame();
|
||||
return Lang.indexWhere(list, instance -> instance.getId().equals(currentId));
|
||||
}, it -> Profiles.setSelectedInstance(profile, it.getId()));
|
||||
}, it -> repository.setSelectedInstance(it.getId()));
|
||||
|
||||
StackPane.setAlignment(launchPane, Pos.BOTTOM_RIGHT);
|
||||
{
|
||||
@@ -259,7 +260,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
JFXPopup.PopupHPosition.RIGHT,
|
||||
0,
|
||||
-menuButton.getHeight(),
|
||||
profile, versions
|
||||
repository, versions
|
||||
));
|
||||
FXUtils.installFastTooltip(menuButton, i18n("version.switch"));
|
||||
menuButton.setGraphic(SVG.ARROW_DROP_UP.createIcon(30));
|
||||
@@ -315,8 +316,8 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
}
|
||||
|
||||
private void launch() {
|
||||
Profile profile = Profiles.getSelectedProfile();
|
||||
Versions.launch(profile, Profiles.getSelectedInstance(profile));
|
||||
HMCLGameRepository repository = GameDirectoryManager.getSelectedRepository();
|
||||
Versions.launch(repository, repository.getSelectedInstance());
|
||||
}
|
||||
|
||||
private void launchNoGame() {
|
||||
@@ -332,8 +333,8 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IOException("No versions found")))
|
||||
.thenComposeAsync(version -> {
|
||||
Profile profile = Profiles.getSelectedProfile();
|
||||
DefaultDependencyManager dependency = profile.getDependency();
|
||||
HMCLGameRepository repository = GameDirectoryManager.getSelectedRepository();
|
||||
DefaultDependencyManager dependency = repository.getDependency();
|
||||
String gameVersion = gameVersionHolder.value = version.getGameVersion();
|
||||
|
||||
return dependency.gameBuilder()
|
||||
@@ -341,10 +342,10 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
.gameVersion(gameVersion)
|
||||
.buildAsync();
|
||||
})
|
||||
.whenComplete(any -> profile.getRepository().refreshVersions())
|
||||
.whenComplete(any -> GameDirectoryManager.getSelectedRepository().refreshVersions())
|
||||
.whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
if (exception == null) {
|
||||
Profiles.setSelectedInstance(profile, gameVersionHolder.value);
|
||||
GameDirectoryManager.getSelectedRepository().setSelectedInstance(gameVersionHolder.value);
|
||||
launch();
|
||||
} else if (exception instanceof CancellationException) {
|
||||
Controllers.showToast(i18n("message.cancelled"));
|
||||
@@ -376,8 +377,12 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
return state;
|
||||
}
|
||||
|
||||
public Profile getProfile() {
|
||||
return profile;
|
||||
public GameDirectory getGameDirectory() {
|
||||
return repository.getGameDirectory();
|
||||
}
|
||||
|
||||
public HMCLGameRepository getRepository() {
|
||||
return repository;
|
||||
}
|
||||
|
||||
public String getCurrentGame() {
|
||||
@@ -432,9 +437,9 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
this.latestVersion.set(latestVersion);
|
||||
}
|
||||
|
||||
public void initVersions(Profile profile, List<Version> versions) {
|
||||
public void initVersions(HMCLGameRepository repository, List<Version> versions) {
|
||||
FXUtils.checkFxUserThread();
|
||||
this.profile = profile;
|
||||
this.repository = repository;
|
||||
this.versions.setAll(versions);
|
||||
}
|
||||
}
|
||||
@@ -27,8 +27,8 @@ import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.GameDirectory;
|
||||
import org.jackhuang.hmcl.setting.GameDirectoryManager;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.terracotta.TerracottaMetadata;
|
||||
@@ -77,9 +77,9 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class)
|
||||
.register(event -> onRefreshedVersions((HMCLGameRepository) event.getSource()));
|
||||
|
||||
Profile profile = Profiles.getSelectedProfile();
|
||||
if (profile != null && profile.getRepository().isLoaded())
|
||||
onRefreshedVersions(Profiles.selectedProfileProperty().get().getRepository());
|
||||
HMCLGameRepository repository = GameDirectoryManager.getSelectedRepository();
|
||||
if (repository.isLoaded())
|
||||
onRefreshedVersions(GameDirectoryManager.getSelectedRepository());
|
||||
|
||||
getStyleClass().remove("gray-background");
|
||||
getLeft().getStyleClass().add("gray-background");
|
||||
@@ -104,7 +104,7 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
Path file = modpacks.get(0);
|
||||
if (ModpackHelper.isFileModpackByExtension(file)) {
|
||||
Controllers.getDecorator().startWizard(
|
||||
new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), file),
|
||||
new ModpackInstallWizardProvider(GameDirectoryManager.getSelectedRepository(), file),
|
||||
i18n("install.modpack"));
|
||||
} else if (NBTFileType.isNBTFileByExtension(file)) {
|
||||
try {
|
||||
@@ -115,15 +115,15 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
i18n("message.error"), MessageDialogPane.MessageType.ERROR);
|
||||
}
|
||||
} else if ("json".equalsIgnoreCase(FileUtils.getExtension(file))) {
|
||||
Versions.installFromJson(Profiles.getSelectedProfile(), file);
|
||||
Versions.installFromJson(GameDirectoryManager.getSelectedRepository(), file);
|
||||
}
|
||||
});
|
||||
|
||||
FXUtils.onChangeAndOperate(Profiles.selectedInstanceProperty(), mainPage::setCurrentGame);
|
||||
FXUtils.onChangeAndOperate(GameDirectoryManager.selectedInstanceProperty(), mainPage::setCurrentGame);
|
||||
mainPage.latestVersionProperty().bind(UpdateChecker.latestVersionProperty());
|
||||
|
||||
Profiles.registerVersionsListener(profile -> {
|
||||
HMCLGameRepository repository = profile.getRepository();
|
||||
GameDirectoryManager.registerVersionsListener(repository -> {
|
||||
GameDirectory gameDirectory = repository.getGameDirectory();
|
||||
List<Version> children = repository.getVersions().parallelStream()
|
||||
.filter(version -> !version.isHidden())
|
||||
.sorted(Comparator
|
||||
@@ -131,8 +131,8 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
.thenComparing(version -> VersionNumber.asVersion(repository.getGameVersion(version).orElse(version.getId()))))
|
||||
.collect(Collectors.toList());
|
||||
runInFX(() -> {
|
||||
if (profile == Profiles.getSelectedProfile())
|
||||
mainPage.initVersions(profile, children);
|
||||
if (gameDirectory == GameDirectoryManager.getSelectedGameDirectory())
|
||||
mainPage.initVersions(repository, children);
|
||||
});
|
||||
});
|
||||
this.mainPage = mainPage;
|
||||
@@ -154,18 +154,17 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
// second item in left sidebar
|
||||
GameAdvancedListItem gameListItem = new GameAdvancedListItem();
|
||||
gameListItem.setOnAction(e -> {
|
||||
Profile profile = Profiles.getSelectedProfile();
|
||||
String version = Profiles.getSelectedInstance();
|
||||
String version = GameDirectoryManager.getSelectedRepository().getSelectedInstance();
|
||||
if (version == null) {
|
||||
Controllers.navigate(Controllers.getGameListPage());
|
||||
} else {
|
||||
Versions.modifyGameSettings(profile, version);
|
||||
Versions.modifyGameSettings(GameDirectoryManager.getSelectedRepository(), version);
|
||||
}
|
||||
});
|
||||
FXUtils.onScroll(gameListItem, getSkinnable().getMainPage().getVersions(), list -> {
|
||||
String currentId = getSkinnable().getMainPage().getCurrentGame();
|
||||
return Lang.indexWhere(list, instance -> instance.getId().equals(currentId));
|
||||
}, it -> Profiles.setSelectedInstance(getSkinnable().getMainPage().getProfile(), it.getId()));
|
||||
}, it -> getSkinnable().getMainPage().getRepository().setSelectedInstance(it.getId()));
|
||||
if (AnimationUtils.isAnimationEnabled()) {
|
||||
FXUtils.prepareOnMouseEnter(gameListItem, Controllers::prepareVersionPage);
|
||||
}
|
||||
@@ -195,7 +194,7 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
launcherSettingsItem.setLeftIcon(SVG.SETTINGS);
|
||||
launcherSettingsItem.setTitle(i18n("settings"));
|
||||
launcherSettingsItem.setOnAction(e -> {
|
||||
Controllers.getSettingsPage().showGameSettings(Profiles.getSelectedProfile());
|
||||
Controllers.getSettingsPage().showGameSettings(GameDirectoryManager.getSelectedRepository());
|
||||
Controllers.navigate(Controllers.getSettingsPage());
|
||||
});
|
||||
if (AnimationUtils.isAnimationEnabled()) {
|
||||
@@ -252,7 +251,7 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
JFXPopup.PopupHPosition.LEFT,
|
||||
gameListItem.getWidth(),
|
||||
0,
|
||||
getSkinnable().getMainPage().getProfile(),
|
||||
getSkinnable().getMainPage().getRepository(),
|
||||
getSkinnable().getMainPage().getVersions());
|
||||
}
|
||||
}
|
||||
@@ -281,7 +280,7 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
Task.supplyAsync(() -> CompressingUtils.findSuitableEncoding(modpackFile))
|
||||
.thenApplyAsync(encoding -> ModpackHelper.readModpackManifest(modpackFile, encoding))
|
||||
.thenApplyAsync(modpack -> ModpackHelper
|
||||
.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack, null)
|
||||
.getInstallTask(repository, modpackFile, modpack.getName(), modpack, null)
|
||||
.executor())
|
||||
.thenAcceptAsync(Schedulers.javafx(), executor -> {
|
||||
Controllers.taskDialog(executor, i18n("modpack.installing"), TaskCancellationAction.NO_CANCEL);
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.profile;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.scene.control.RadioButton;
|
||||
import javafx.scene.control.Skin;
|
||||
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class ProfileListItem extends RadioButton {
|
||||
private final Profile profile;
|
||||
private final StringProperty title = new SimpleStringProperty();
|
||||
private final StringProperty subtitle = new SimpleStringProperty();
|
||||
|
||||
public ProfileListItem(Profile profile) {
|
||||
this.profile = profile;
|
||||
getStyleClass().setAll("profile-list-item", "navigation-drawer-item");
|
||||
setUserData(profile);
|
||||
|
||||
title.set(Profiles.getProfileDisplayName(profile));
|
||||
subtitle.set(profile.getPath().toString());
|
||||
|
||||
this.selectedProperty().bind(Bindings.equal(profile, Profiles.selectedProfileProperty()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new ProfileListItemSkin(this);
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
if (!Profiles.canRemoveProfile(profile)) {
|
||||
Controllers.confirmBackupAndOverwrite(i18n("settings.game_directories.read_only"), () -> {
|
||||
Profiles.forceOverwriteProfileFiles(profile);
|
||||
Profiles.removeProfile(profile);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Profiles.removeProfile(profile);
|
||||
}
|
||||
|
||||
public Profile getProfile() {
|
||||
return profile;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title.set(title);
|
||||
}
|
||||
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public String getSubtitle() {
|
||||
return subtitle.get();
|
||||
}
|
||||
|
||||
public void setSubtitle(String subtitle) {
|
||||
this.subtitle.set(subtitle);
|
||||
}
|
||||
|
||||
public StringProperty subtitleProperty() {
|
||||
return subtitle;
|
||||
}
|
||||
}
|
||||
@@ -38,8 +38,7 @@ import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import org.jackhuang.hmcl.game.LauncherHelper;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.GameDirectoryManager;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.terracotta.TerracottaManager;
|
||||
@@ -213,8 +212,8 @@ public class TerracottaControllerPage extends StackPane {
|
||||
i18n("terracotta.status.waiting.host.launch.title"),
|
||||
MessageDialogPane.MessageType.QUESTION
|
||||
).addAction(i18n("version.launch"), () -> {
|
||||
Profile profile = Profiles.getSelectedProfile();
|
||||
Versions.launch(profile, Profiles.getSelectedInstance(profile), launcherHelper -> {
|
||||
var repository = GameDirectoryManager.getSelectedRepository();
|
||||
Versions.launch(repository, repository.getSelectedInstance(), launcherHelper -> {
|
||||
launcherHelper.setKeep();
|
||||
launcherHelper.setDisableOfflineSkin();
|
||||
});
|
||||
|
||||
@@ -78,13 +78,13 @@ public class TerracottaPage extends DecoratorAnimatedPage implements DecoratorPa
|
||||
AdvancedListBox toolbar = new AdvancedListBox()
|
||||
.add(accountListItem)
|
||||
.addNavigationDrawerItem(i18n("version.launch"), SVG.ROCKET_LAUNCH, () -> {
|
||||
Profile profile = Profiles.getSelectedProfile();
|
||||
Versions.launch(profile, Profiles.getSelectedInstance(profile), launcherHelper -> {
|
||||
var repository = GameDirectoryManager.getSelectedRepository();
|
||||
Versions.launch(repository, repository.getSelectedInstance(), launcherHelper -> {
|
||||
launcherHelper.setKeep();
|
||||
launcherHelper.setDisableOfflineSkin();
|
||||
});
|
||||
}, item -> {
|
||||
instanceChangeListenerHolder = FXUtils.onWeakChangeAndOperate(Profiles.selectedInstanceProperty(),
|
||||
instanceChangeListenerHolder = FXUtils.onWeakChangeAndOperate(GameDirectoryManager.selectedInstanceProperty(),
|
||||
instanceName -> item.setSubtitle(StringUtils.isNotBlank(instanceName) ? instanceName : i18n("version.empty"))
|
||||
);
|
||||
|
||||
@@ -92,14 +92,14 @@ public class TerracottaPage extends DecoratorAnimatedPage implements DecoratorPa
|
||||
FXUtils.onScroll(item, mainPage.getVersions(), list -> {
|
||||
String currentId = mainPage.getCurrentGame();
|
||||
return Lang.indexWhere(list, instance -> instance.getId().equals(currentId));
|
||||
}, it -> Profiles.setSelectedInstance(mainPage.getProfile(), it.getId()));
|
||||
}, it -> mainPage.getRepository().setSelectedInstance(it.getId()));
|
||||
|
||||
FXUtils.onSecondaryButtonClicked(item, () -> GameListPopupMenu.show(item,
|
||||
JFXPopup.PopupVPosition.BOTTOM,
|
||||
JFXPopup.PopupHPosition.LEFT,
|
||||
item.getWidth(),
|
||||
0,
|
||||
mainPage.getProfile(), mainPage.getVersions()));
|
||||
mainPage.getRepository(), mainPage.getVersions()));
|
||||
})
|
||||
.addNavigationDrawerItem(i18n("terracotta.feedback.title"), SVG.FEEDBACK, () -> FXUtils.openLink(TerracottaMetadata.FEEDBACK_LINK));
|
||||
BorderPane.setMargin(toolbar, new Insets(0, 0, 12, 0));
|
||||
|
||||
@@ -38,13 +38,12 @@ import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.*;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.addon.RemoteAddon;
|
||||
import org.jackhuang.hmcl.addon.RemoteAddonRepository;
|
||||
import org.jackhuang.hmcl.addon.repository.ModrinthRemoteAddonRepository;
|
||||
import org.jackhuang.hmcl.setting.DownloadProviders;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
@@ -57,6 +56,7 @@ import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.*;
|
||||
@@ -67,12 +67,12 @@ import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.selectedItemPropertyFor;
|
||||
|
||||
public class DownloadListPage extends Control implements DecoratorPage, VersionPage.VersionLoadable {
|
||||
public class DownloadListPage extends Control implements DecoratorPage, VersionPage.GameInstanceLoadable {
|
||||
protected final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();
|
||||
private final BooleanProperty loading = new SimpleBooleanProperty(false);
|
||||
private final BooleanProperty failed = new SimpleBooleanProperty(false);
|
||||
private final boolean versionSelection;
|
||||
private final ObjectProperty<Profile.ProfileVersion> version = new SimpleObjectProperty<>();
|
||||
private final ObjectProperty<HMCLGameRepository.InstanceReference> instanceReference = new SimpleObjectProperty<>();
|
||||
private final IntegerProperty pageOffset = new SimpleIntegerProperty(0);
|
||||
private final IntegerProperty pageCount = new SimpleIntegerProperty(-1);
|
||||
private final ListProperty<RemoteAddon> items = new SimpleListProperty<>(this, "items", FXCollections.observableArrayList());
|
||||
@@ -111,8 +111,8 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadVersion(Profile profile, String version) {
|
||||
this.version.set(new Profile.ProfileVersion(profile, version));
|
||||
public void loadInstance(HMCLGameRepository repository, @Nullable String instanceId) {
|
||||
this.instanceReference.set(new HMCLGameRepository.InstanceReference(repository, instanceId));
|
||||
|
||||
setLoading(false);
|
||||
setFailed(false);
|
||||
@@ -123,10 +123,10 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
}
|
||||
|
||||
if (versionSelection) {
|
||||
versions.setAll(profile.getRepository().getDisplayVersions()
|
||||
versions.setAll(repository.getDisplayVersions()
|
||||
.map(Version::getId)
|
||||
.collect(Collectors.toList()));
|
||||
selectedVersion.set(Profiles.getSelectedInstance(profile));
|
||||
selectedVersion.set(repository.getSelectedInstance());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,12 +165,12 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
|
||||
int currentSearchID = searchID = searchID + 1;
|
||||
Task.supplyAsync(() -> {
|
||||
Profile.ProfileVersion version = this.version.get();
|
||||
if (StringUtils.isBlank(version.version())) {
|
||||
HMCLGameRepository.InstanceReference instanceReference = this.instanceReference.get();
|
||||
if (StringUtils.isBlank(instanceReference.instanceId())) {
|
||||
return userGameVersion;
|
||||
} else {
|
||||
return StringUtils.isNotBlank(version.version())
|
||||
? version.profile().getRepository().getGameVersion(version.version()).orElse("")
|
||||
return StringUtils.isNotBlank(instanceReference.instanceId())
|
||||
? instanceReference.repository().getGameVersion(instanceReference.instanceId()).orElse("")
|
||||
: "";
|
||||
}
|
||||
}).thenApplyAsync(
|
||||
@@ -218,11 +218,11 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
}
|
||||
}
|
||||
|
||||
protected Profile.ProfileVersion getProfileVersion() {
|
||||
protected HMCLGameRepository.InstanceReference getInstanceReference() {
|
||||
if (versionSelection) {
|
||||
return new Profile.ProfileVersion(version.get().profile(), selectedVersion.get());
|
||||
return new HMCLGameRepository.InstanceReference(instanceReference.get().repository(), selectedVersion.get());
|
||||
} else {
|
||||
return version.get();
|
||||
return instanceReference.get();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,16 +320,16 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
Label lblGameVersion = new Label(i18n("world.game_version"));
|
||||
searchPane.addRow(rowIndex++, new Label(i18n("mods.name")), nameField, lblGameVersion, gameVersionField);
|
||||
|
||||
ObjectBinding<Boolean> hasVersion = BindingMapping.of(getSkinnable().version)
|
||||
.map(version -> version.version() == null);
|
||||
ObjectBinding<Boolean> hasVersion = BindingMapping.of(getSkinnable().instanceReference)
|
||||
.map(instanceReference -> instanceReference.instanceId() == null);
|
||||
lblGameVersion.managedProperty().bind(hasVersion);
|
||||
lblGameVersion.visibleProperty().bind(hasVersion);
|
||||
gameVersionField.managedProperty().bind(hasVersion);
|
||||
gameVersionField.visibleProperty().bind(hasVersion);
|
||||
FXUtils.installFastTooltip(gameVersionField, i18n("search.enter"));
|
||||
|
||||
FXUtils.onChangeAndOperate(getSkinnable().version, version -> {
|
||||
if (StringUtils.isNotBlank(version.version())) {
|
||||
FXUtils.onChangeAndOperate(getSkinnable().instanceReference, instanceReference -> {
|
||||
if (StringUtils.isNotBlank(instanceReference.instanceId())) {
|
||||
GridPane.setColumnSpan(nameField, 3);
|
||||
} else {
|
||||
GridPane.setColumnSpan(nameField, 1);
|
||||
@@ -561,7 +561,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
FXUtils.onClicked(wrapper, () -> {
|
||||
RemoteAddon item = getItem();
|
||||
if (item != null)
|
||||
Controllers.navigate(new DownloadPage(getSkinnable(), item, getSkinnable().getProfileVersion(), getSkinnable().callback));
|
||||
Controllers.navigate(new DownloadPage(getSkinnable(), item, getSkinnable().getInstanceReference(), getSkinnable().callback));
|
||||
});
|
||||
|
||||
setPrefWidth(0);
|
||||
|
||||
@@ -37,7 +37,6 @@ import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.addon.mod.ModLoaderType;
|
||||
import org.jackhuang.hmcl.addon.RemoteAddon;
|
||||
import org.jackhuang.hmcl.addon.RemoteAddonRepository;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
@@ -70,21 +69,21 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
private final ModTranslations translations;
|
||||
private final RemoteAddon addon;
|
||||
private final ModTranslations.Mod mod;
|
||||
private final Profile.ProfileVersion version;
|
||||
private final HMCLGameRepository.InstanceReference instanceReference;
|
||||
private final DownloadCallback callback;
|
||||
private final DownloadListPage page;
|
||||
private final RemoteAddonRepository.Type type;
|
||||
|
||||
private SimpleMultimap<String, RemoteAddon.Version, List<RemoteAddon.Version>> versions;
|
||||
|
||||
public DownloadPage(DownloadListPage page, RemoteAddon addon, Profile.ProfileVersion version, @Nullable DownloadCallback callback) {
|
||||
public DownloadPage(DownloadListPage page, RemoteAddon addon, HMCLGameRepository.InstanceReference instanceReference, @Nullable DownloadCallback callback) {
|
||||
this.page = page;
|
||||
this.repository = page.repository;
|
||||
this.addon = addon;
|
||||
this.type = Objects.requireNonNullElse(addon.repoType(), repository.getType());
|
||||
this.translations = ModTranslations.getTranslationsByRepositoryType(this.type);
|
||||
this.mod = translations.getModByCurseForgeId(addon.slug());
|
||||
this.version = version;
|
||||
this.instanceReference = instanceReference;
|
||||
this.callback = callback;
|
||||
loadAddonVersions();
|
||||
|
||||
@@ -131,8 +130,8 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
return addon;
|
||||
}
|
||||
|
||||
public Profile.ProfileVersion getVersion() {
|
||||
return version;
|
||||
public HMCLGameRepository.InstanceReference getInstanceReference() {
|
||||
return instanceReference;
|
||||
}
|
||||
|
||||
public boolean isLoading() {
|
||||
@@ -163,7 +162,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
if (this.callback == null) {
|
||||
saveAs(file);
|
||||
} else {
|
||||
this.callback.download(page.getDownloadProvider(), version.profile(), version.version(), addon, file);
|
||||
this.callback.download(page.getDownloadProvider(), instanceReference.repository(), instanceReference.instanceId(), addon, file);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,9 +271,9 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
FXUtils.onChangeAndOperate(control.loaded, loaded -> {
|
||||
if (control.versions == null) return;
|
||||
|
||||
if (control.version.profile() != null && control.version.version() != null) {
|
||||
HMCLGameRepository repository = control.version.profile().getRepository();
|
||||
Version game = repository.getResolvedPreservingPatchesVersion(control.version.version());
|
||||
if (control.instanceReference.repository() != null && control.instanceReference.instanceId() != null) {
|
||||
HMCLGameRepository repository = control.instanceReference.repository();
|
||||
Version game = repository.getResolvedPreservingPatchesVersion(control.instanceReference.instanceId());
|
||||
String gameVersion = repository.getGameVersion(game).orElse(null);
|
||||
|
||||
if (gameVersion != null && control.versions.containsKey(gameVersion)) {
|
||||
@@ -374,7 +373,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
Pair.pair(RemoteAddon.DependencyType.BROKEN, "addon.dependency.broken")
|
||||
));
|
||||
|
||||
DependencyAddonItem(DownloadListPage page, RemoteAddon addon, Profile.ProfileVersion version) {
|
||||
DependencyAddonItem(DownloadListPage page, RemoteAddon addon, HMCLGameRepository.InstanceReference instanceReference) {
|
||||
HBox pane = new HBox(8);
|
||||
pane.setPadding(new Insets(0, 8, 0, 8));
|
||||
pane.setAlignment(Pos.CENTER_LEFT);
|
||||
@@ -394,7 +393,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
};
|
||||
setOnAction((e) -> {
|
||||
fireEvent(new DialogCloseEvent());
|
||||
Controllers.navigate(new DownloadPage(page, addon, version, callback));
|
||||
Controllers.navigate(new DownloadPage(page, addon, instanceReference, callback));
|
||||
});
|
||||
setNode(IDX_LEADING, pane);
|
||||
|
||||
@@ -587,7 +586,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
if (dep == RemoteAddon.BROKEN) {
|
||||
return;
|
||||
}
|
||||
DependencyAddonItem dependencyAddonItem = new DependencyAddonItem(selfPage.page, dep, selfPage.version);
|
||||
DependencyAddonItem dependencyAddonItem = new DependencyAddonItem(selfPage.page, dep, selfPage.instanceReference);
|
||||
dependencies.get(dependency.getType()).add(dependencyAddonItem);
|
||||
})
|
||||
.setSignificance(Task.TaskSignificance.MINOR));
|
||||
@@ -611,6 +610,6 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
|
||||
@FunctionalInterface
|
||||
public interface DownloadCallback {
|
||||
void download(DownloadProvider downloadProvider, Profile profile, @Nullable String version, RemoteAddon addon, RemoteAddon.Version file);
|
||||
void download(DownloadProvider downloadProvider, HMCLGameRepository repository, @Nullable String version, RemoteAddon addon, RemoteAddon.Version file);
|
||||
}
|
||||
}
|
||||
@@ -19,8 +19,8 @@ package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import javafx.geometry.Pos;
|
||||
import org.jackhuang.hmcl.event.Event;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.setting.GameDirectoryManager;
|
||||
import org.jackhuang.hmcl.setting.VersionIconType;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||
@@ -34,7 +34,7 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
public class GameAdvancedListItem extends AdvancedListItem {
|
||||
private final ImageContainer imageContainer;
|
||||
private final WeakListenerHolder holder = new WeakListenerHolder();
|
||||
private Profile profile;
|
||||
private HMCLGameRepository repository;
|
||||
@SuppressWarnings("unused")
|
||||
private Consumer<Event> onVersionIconChangedListener;
|
||||
|
||||
@@ -44,23 +44,22 @@ public class GameAdvancedListItem extends AdvancedListItem {
|
||||
AdvancedListItem.setAlignment(imageContainer, Pos.CENTER);
|
||||
setLeftGraphic(imageContainer);
|
||||
|
||||
holder.add(FXUtils.onWeakChangeAndOperate(Profiles.selectedInstanceProperty(), this::loadVersion));
|
||||
holder.add(FXUtils.onWeakChangeAndOperate(GameDirectoryManager.selectedInstanceProperty(), this::loadVersion));
|
||||
}
|
||||
|
||||
private void loadVersion(String version) {
|
||||
if (Profiles.getSelectedProfile() != profile) {
|
||||
profile = Profiles.getSelectedProfile();
|
||||
if (profile != null) {
|
||||
onVersionIconChangedListener = profile.getRepository().onVersionIconChanged.registerWeak(event -> {
|
||||
this.loadVersion(Profiles.getSelectedInstance());
|
||||
if (GameDirectoryManager.getSelectedRepository() != repository) {
|
||||
repository = GameDirectoryManager.getSelectedRepository();
|
||||
if (repository != null) {
|
||||
onVersionIconChangedListener = repository.onVersionIconChanged.registerWeak(event -> {
|
||||
this.loadVersion(repository.getSelectedInstance());
|
||||
});
|
||||
}
|
||||
}
|
||||
if (version != null && Profiles.getSelectedProfile() != null &&
|
||||
Profiles.getSelectedProfile().getRepository().hasVersion(version)) {
|
||||
if (version != null && repository != null && repository.hasVersion(version)) {
|
||||
setTitle(i18n("version.manage.manage"));
|
||||
setSubtitle(version);
|
||||
imageContainer.setImage(Profiles.getSelectedProfile().getRepository().getVersionIconImage(version));
|
||||
imageContainer.setImage(repository.getVersionIconImage(version));
|
||||
} else {
|
||||
setTitle(i18n("version.empty"));
|
||||
setSubtitle(i18n("version.empty.add"));
|
||||
|
||||
@@ -20,8 +20,9 @@ package org.jackhuang.hmcl.ui.versions;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.scene.image.Image;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.modpack.ModpackConfiguration;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.GameDirectory;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -41,7 +42,7 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
public class GameItem {
|
||||
private static final ThreadPoolExecutor POOL_VERSION_RESOLVE = threadPool("VersionResolve", true, 1, 10, TimeUnit.SECONDS);
|
||||
|
||||
protected final Profile profile;
|
||||
protected final HMCLGameRepository repository;
|
||||
protected final String id;
|
||||
|
||||
private boolean initialized = false;
|
||||
@@ -50,13 +51,17 @@ public class GameItem {
|
||||
private StringProperty subtitle;
|
||||
private ObjectProperty<Image> image;
|
||||
|
||||
public GameItem(Profile profile, String id) {
|
||||
this.profile = profile;
|
||||
public GameItem(HMCLGameRepository repository, String id) {
|
||||
this.repository = repository;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Profile getProfile() {
|
||||
return profile;
|
||||
public GameDirectory getGameDirectory() {
|
||||
return repository.getGameDirectory();
|
||||
}
|
||||
|
||||
public HMCLGameRepository getRepository() {
|
||||
return repository;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
@@ -78,10 +83,10 @@ public class GameItem {
|
||||
|
||||
CompletableFuture.supplyAsync(() -> {
|
||||
// GameVersion.minecraftVersion() is a time-costing job (up to ~200 ms)
|
||||
Optional<String> gameVersion = profile.getRepository().getGameVersion(id);
|
||||
Optional<String> gameVersion = repository.getGameVersion(id);
|
||||
String modPackVersion = null;
|
||||
try {
|
||||
ModpackConfiguration<?> config = profile.getRepository().readModpackConfiguration(id);
|
||||
ModpackConfiguration<?> config = repository.readModpackConfiguration(id);
|
||||
modPackVersion = config != null ? config.getVersion() : null;
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to read modpack configuration from " + id, e);
|
||||
@@ -94,7 +99,7 @@ public class GameItem {
|
||||
}
|
||||
|
||||
StringBuilder libraries = new StringBuilder(Objects.requireNonNullElse(result.gameVersion, i18n("message.unknown")));
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(id), result.gameVersion);
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(repository.getResolvedPreservingPatchesVersion(id), result.gameVersion);
|
||||
for (LibraryAnalyzer.LibraryMark mark : analyzer) {
|
||||
String libraryId = mark.getLibraryId();
|
||||
String libraryVersion = mark.getLibraryVersion();
|
||||
@@ -113,7 +118,7 @@ public class GameItem {
|
||||
}, Schedulers.javafx());
|
||||
|
||||
title.set(id);
|
||||
image.set(profile.getRepository().getVersionIconImage(id));
|
||||
image.set(repository.getVersionIconImage(id));
|
||||
}
|
||||
|
||||
public ReadOnlyStringProperty titleProperty() {
|
||||
|
||||
@@ -29,7 +29,6 @@ import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
@@ -70,7 +69,7 @@ public final class GameListCell extends ListCell<GameListItem> {
|
||||
fireEvent(new ActionEvent());
|
||||
GameListItem item = GameListCell.this.getItem();
|
||||
if (item != null) {
|
||||
Profiles.setSelectedInstance(item.getProfile(), item.getId());
|
||||
item.getRepository().setSelectedInstance(item.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@ import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.setting.GameDirectoryManager;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -30,13 +30,13 @@ public class GameListItem extends GameItem {
|
||||
private final boolean isModpack;
|
||||
private final BooleanProperty selected = new SimpleBooleanProperty(this, "selected");
|
||||
|
||||
public GameListItem(Profile profile, String id) {
|
||||
super(profile, id);
|
||||
this.isModpack = profile.getRepository().isModpack(id);
|
||||
public GameListItem(HMCLGameRepository repository, String id) {
|
||||
super(repository, id);
|
||||
this.isModpack = repository.isModpack(id);
|
||||
selected.bind(Bindings.createBooleanBinding(
|
||||
() -> profile == Profiles.getSelectedProfile() && Objects.equals(Profiles.getSelectedInstance(profile), id),
|
||||
Profiles.selectedProfileProperty(),
|
||||
Profiles.selectedInstanceProperty()));
|
||||
() -> repository.getGameDirectory() == GameDirectoryManager.getSelectedGameDirectory() && Objects.equals(repository.getSelectedInstance(), id),
|
||||
GameDirectoryManager.selectedGameDirectoryProperty(),
|
||||
GameDirectoryManager.selectedInstanceProperty()));
|
||||
}
|
||||
|
||||
public ReadOnlyBooleanProperty selectedProperty() {
|
||||
@@ -44,39 +44,39 @@ public class GameListItem extends GameItem {
|
||||
}
|
||||
|
||||
public void rename() {
|
||||
Versions.renameVersion(profile, id);
|
||||
Versions.renameVersion(repository, id);
|
||||
}
|
||||
|
||||
public void duplicate() {
|
||||
Versions.duplicateVersion(profile, id);
|
||||
Versions.duplicateVersion(repository, id);
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
Versions.deleteVersion(profile, id);
|
||||
Versions.deleteVersion(repository, id);
|
||||
}
|
||||
|
||||
public void export() {
|
||||
Versions.exportVersion(profile, id);
|
||||
Versions.exportVersion(repository, id);
|
||||
}
|
||||
|
||||
public void browse() {
|
||||
Versions.openFolder(profile, id);
|
||||
Versions.openFolder(repository, id);
|
||||
}
|
||||
|
||||
public void testGame() {
|
||||
Versions.testGame(profile, id);
|
||||
Versions.testGame(repository, id);
|
||||
}
|
||||
|
||||
public void launch() {
|
||||
Versions.launch(profile, id);
|
||||
Versions.launch(repository, id);
|
||||
}
|
||||
|
||||
public void modifyGameSettings() {
|
||||
Versions.modifyGameSettings(profile, id);
|
||||
Versions.modifyGameSettings(repository, id);
|
||||
}
|
||||
|
||||
public void generateLaunchScript() {
|
||||
Versions.generateLaunchScript(profile, id);
|
||||
Versions.generateLaunchScript(repository, id);
|
||||
}
|
||||
|
||||
public boolean canUpdate() {
|
||||
@@ -84,6 +84,6 @@ public class GameListItem extends GameItem {
|
||||
}
|
||||
|
||||
public void update() {
|
||||
Versions.updateVersion(profile, id);
|
||||
Versions.updateVersion(repository, id);
|
||||
}
|
||||
}
|
||||
@@ -39,9 +39,9 @@ import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.GameDirectoryManager;
|
||||
import org.jackhuang.hmcl.ui.*;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
@@ -52,8 +52,8 @@ import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
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.profile.ProfileListItem;
|
||||
import org.jackhuang.hmcl.ui.profile.ProfilePage;
|
||||
import org.jackhuang.hmcl.ui.directory.GameDirectoryListItem;
|
||||
import org.jackhuang.hmcl.ui.directory.GameDirectoryPage;
|
||||
import org.jackhuang.hmcl.util.FXThread;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||
@@ -71,12 +71,13 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class GameListPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("version.manage")));
|
||||
/// Navigation drawer items for configured game directories.
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private final ObservableList<ProfileListItem> profileListItems;
|
||||
private final ObservableList<GameDirectoryListItem> gameDirectoryListItems;
|
||||
|
||||
public GameListPage() {
|
||||
profileListItems = MappedObservableList.create(Profiles.getProfiles(), profile -> {
|
||||
ProfileListItem item = new ProfileListItem(profile);
|
||||
gameDirectoryListItems = MappedObservableList.create(GameDirectoryManager.getGameDirectories(), gameDirectory -> {
|
||||
GameDirectoryListItem item = new GameDirectoryListItem(gameDirectory);
|
||||
FXUtils.setLimitWidth(item, 200);
|
||||
return item;
|
||||
});
|
||||
@@ -85,19 +86,19 @@ public class GameListPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
ScrollPane pane = new ScrollPane();
|
||||
VBox.setVgrow(pane, Priority.ALWAYS);
|
||||
{
|
||||
AdvancedListItem addProfileItem = new AdvancedListItem();
|
||||
addProfileItem.getStyleClass().add("navigation-drawer-item");
|
||||
addProfileItem.setTitle(i18n("profile.new"));
|
||||
addProfileItem.setLeftIcon(SVG.ADD_CIRCLE);
|
||||
addProfileItem.setOnAction(e -> Controllers.navigate(new ProfilePage(null)));
|
||||
AdvancedListItem addGameDirectoryItem = new AdvancedListItem();
|
||||
addGameDirectoryItem.getStyleClass().add("navigation-drawer-item");
|
||||
addGameDirectoryItem.setTitle(i18n("game_directory.new"));
|
||||
addGameDirectoryItem.setLeftIcon(SVG.ADD_CIRCLE);
|
||||
addGameDirectoryItem.setOnAction(e -> Controllers.navigate(new GameDirectoryPage(null)));
|
||||
|
||||
pane.setFitToWidth(true);
|
||||
VBox wrapper = new VBox();
|
||||
wrapper.getStyleClass().add("advanced-list-box-content");
|
||||
VBox box = new VBox();
|
||||
box.setFillWidth(true);
|
||||
Bindings.bindContent(box.getChildren(), profileListItems);
|
||||
wrapper.getChildren().setAll(box, addProfileItem);
|
||||
Bindings.bindContent(box.getChildren(), gameDirectoryListItems);
|
||||
wrapper.getChildren().setAll(box, addGameDirectoryItem);
|
||||
pane.setContent(wrapper);
|
||||
}
|
||||
|
||||
@@ -115,15 +116,15 @@ public class GameListPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
Path file = files.get(0);
|
||||
|
||||
if (ModpackHelper.isFileModpackByExtension(file)) {
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), file), i18n("install.modpack"));
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(GameDirectoryManager.getSelectedRepository(), file), i18n("install.modpack"));
|
||||
} else if ("json".equalsIgnoreCase(FileUtils.getExtension(file))) {
|
||||
Versions.installFromJson(Profiles.getSelectedProfile(), file);
|
||||
Versions.installFromJson(GameDirectoryManager.getSelectedRepository(), file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void modifyGlobalGameSettings() {
|
||||
Versions.modifyGlobalSettings(Profiles.getSelectedProfile());
|
||||
Versions.modifyGlobalSettings(GameDirectoryManager.getSelectedRepository());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -140,18 +141,18 @@ public class GameListPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
public GameList() {
|
||||
setItems(filteredList);
|
||||
|
||||
Profiles.registerVersionsListener(this::loadVersions);
|
||||
GameDirectoryManager.registerVersionsListener(this::loadVersions);
|
||||
|
||||
setOnFailedAction(e -> Controllers.navigate(Controllers.getDownloadPage()));
|
||||
}
|
||||
|
||||
@FXThread
|
||||
private void loadVersions(Profile profile) {
|
||||
private void loadVersions(HMCLGameRepository repository) {
|
||||
listenerHolder.clear();
|
||||
setLoading(true);
|
||||
setFailedReason(null);
|
||||
|
||||
List<GameListItem> versionItems = profile.getRepository().getDisplayVersions().map(instance -> new GameListItem(profile, instance.getId())).toList();
|
||||
List<GameListItem> versionItems = repository.getDisplayVersions().map(instance -> new GameListItem(repository, instance.getId())).toList();
|
||||
|
||||
sourceList.setAll(versionItems);
|
||||
|
||||
@@ -181,7 +182,7 @@ public class GameListPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
}
|
||||
|
||||
public void refreshList() {
|
||||
Profiles.getSelectedProfile().getRepository().refreshVersionsAsync().start();
|
||||
GameDirectoryManager.getSelectedRepository().refreshVersionsAsync().start();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -33,9 +33,8 @@ import javafx.scene.control.ListView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.ImageContainer;
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
@@ -51,11 +50,11 @@ public final class GameListPopupMenu extends StackPane {
|
||||
|
||||
public static void show(Node owner, JFXPopup.PopupVPosition vAlign, JFXPopup.PopupHPosition hAlign,
|
||||
double initOffsetX, double initOffsetY,
|
||||
Profile profile, List<Version> versions) {
|
||||
HMCLGameRepository repository, List<Version> versions) {
|
||||
GameListPopupMenu menu = new GameListPopupMenu();
|
||||
menu.getItems().setAll(versions.stream()
|
||||
.filter(it -> profile.getRepository().hasVersion(it.getId()))
|
||||
.map(it -> new GameItem(profile, it.getId()))
|
||||
.filter(it -> repository.hasVersion(it.getId()))
|
||||
.map(it -> new GameItem(repository, it.getId()))
|
||||
.toList());
|
||||
JFXPopup popup = new JFXPopup(menu);
|
||||
popup.show(owner, vAlign, hAlign, initOffsetX, initOffsetY);
|
||||
@@ -128,7 +127,7 @@ public final class GameListPopupMenu extends StackPane {
|
||||
FXUtils.onClicked(rootPane, () -> {
|
||||
GameItem item = getItem();
|
||||
if (item != null) {
|
||||
Profiles.setSelectedInstance(item.getProfile(), item.getId());
|
||||
item.getRepository().setSelectedInstance(item.getId());
|
||||
if (getScene().getWindow() instanceof JFXPopup popup)
|
||||
popup.hide();
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@ import javafx.scene.Node;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
@@ -42,8 +42,8 @@ import java.util.concurrent.CompletableFuture;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class InstallerListPage extends ListPageBase<InstallerItem> implements VersionPage.VersionLoadable {
|
||||
private Profile profile;
|
||||
public class InstallerListPage extends ListPageBase<InstallerItem> implements VersionPage.GameInstanceLoadable {
|
||||
private HMCLGameRepository repository;
|
||||
private String versionId;
|
||||
private Version version;
|
||||
private String gameVersion;
|
||||
@@ -61,16 +61,16 @@ public class InstallerListPage extends ListPageBase<InstallerItem> implements Ve
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadVersion(Profile profile, String versionId) {
|
||||
this.profile = profile;
|
||||
this.versionId = versionId;
|
||||
this.version = profile.getRepository().getVersion(versionId);
|
||||
public void loadInstance(HMCLGameRepository repository, String instanceId) {
|
||||
this.repository = repository;
|
||||
this.versionId = instanceId;
|
||||
this.version = repository.getVersion(instanceId);
|
||||
this.gameVersion = null;
|
||||
|
||||
CompletableFuture.supplyAsync(() -> {
|
||||
gameVersion = profile.getRepository().getGameVersion(version).orElse(null);
|
||||
gameVersion = repository.getGameVersion(version).orElse(null);
|
||||
|
||||
return LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(versionId), gameVersion);
|
||||
return LibraryAnalyzer.analyze(repository.getResolvedPreservingPatchesVersion(instanceId), gameVersion);
|
||||
}).thenAcceptAsync(analyzer -> {
|
||||
itemsProperty().clear();
|
||||
|
||||
@@ -98,13 +98,13 @@ public class InstallerListPage extends ListPageBase<InstallerItem> implements Ve
|
||||
}
|
||||
|
||||
item.setOnInstall(() -> {
|
||||
Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, libraryId, libraryVersion));
|
||||
Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(repository, gameVersion, version, libraryId, libraryVersion));
|
||||
});
|
||||
|
||||
item.setOnRemove(() -> profile.getDependency().removeLibraryAsync(version, libraryId)
|
||||
.thenComposeAsync(profile.getRepository()::saveAsync)
|
||||
.withComposeAsync(profile.getRepository().refreshVersionsAsync())
|
||||
.withRunAsync(Schedulers.javafx(), () -> loadVersion(this.profile, this.versionId))
|
||||
item.setOnRemove(() -> repository.getDependency().removeLibraryAsync(version, libraryId)
|
||||
.thenComposeAsync(repository::saveAsync)
|
||||
.withComposeAsync(repository.refreshVersionsAsync())
|
||||
.withRunAsync(Schedulers.javafx(), () -> loadInstance(this.repository, this.versionId))
|
||||
.start());
|
||||
|
||||
itemsProperty().add(item);
|
||||
@@ -123,10 +123,10 @@ public class InstallerListPage extends ListPageBase<InstallerItem> implements Ve
|
||||
|
||||
InstallerItem installerItem = new InstallerItem(libraryId, InstallerItem.Style.LIST_ITEM);
|
||||
installerItem.versionProperty().set(new InstallerItem.InstalledState(libraryVersion, false, false));
|
||||
installerItem.setOnRemove(() -> profile.getDependency().removeLibraryAsync(version, libraryId)
|
||||
.thenComposeAsync(profile.getRepository()::saveAsync)
|
||||
.withComposeAsync(profile.getRepository().refreshVersionsAsync())
|
||||
.withRunAsync(Schedulers.javafx(), () -> loadVersion(this.profile, this.versionId))
|
||||
installerItem.setOnRemove(() -> repository.getDependency().removeLibraryAsync(version, libraryId)
|
||||
.thenComposeAsync(repository::saveAsync)
|
||||
.withComposeAsync(repository.refreshVersionsAsync())
|
||||
.withRunAsync(Schedulers.javafx(), () -> loadInstance(this.repository, this.versionId))
|
||||
.start());
|
||||
|
||||
itemsProperty().add(installerItem);
|
||||
@@ -142,16 +142,16 @@ public class InstallerListPage extends ListPageBase<InstallerItem> implements Ve
|
||||
}
|
||||
|
||||
private void doInstallOffline(Path file) {
|
||||
Task<?> task = profile.getDependency().installLibraryAsync(version, file)
|
||||
.thenComposeAsync(profile.getRepository()::saveAsync)
|
||||
.thenComposeAsync(profile.getRepository().refreshVersionsAsync());
|
||||
Task<?> task = repository.getDependency().installLibraryAsync(version, file)
|
||||
.thenComposeAsync(repository::saveAsync)
|
||||
.thenComposeAsync(repository.refreshVersionsAsync());
|
||||
task.setName(i18n("install.installer.install_offline"));
|
||||
TaskExecutor executor = task.executor(new TaskListener() {
|
||||
@Override
|
||||
public void onStop(boolean success, TaskExecutor executor) {
|
||||
runInFX(() -> {
|
||||
if (success) {
|
||||
loadVersion(profile, versionId);
|
||||
loadInstance(repository, versionId);
|
||||
Controllers.dialog(i18n("install.success"));
|
||||
} else {
|
||||
if (executor.getException() == null)
|
||||
|
||||
@@ -29,7 +29,7 @@ import org.jackhuang.hmcl.addon.mod.LocalModFile;
|
||||
import org.jackhuang.hmcl.addon.mod.ModLoaderType;
|
||||
import org.jackhuang.hmcl.addon.mod.ModManager;
|
||||
import org.jackhuang.hmcl.setting.DownloadProviders;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.GameDirectory;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
@@ -51,13 +51,13 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObject> implements VersionPage.VersionLoadable, PageAware {
|
||||
public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObject> implements VersionPage.GameInstanceLoadable, PageAware {
|
||||
private final BooleanProperty modded = new SimpleBooleanProperty(this, "modded", false);
|
||||
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
|
||||
private ModManager modManager;
|
||||
private Profile profile;
|
||||
private HMCLGameRepository repository;
|
||||
private String instanceId;
|
||||
private String gameVersion;
|
||||
|
||||
@@ -86,16 +86,15 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadVersion(Profile profile, String id) {
|
||||
this.profile = profile;
|
||||
this.instanceId = id;
|
||||
public void loadInstance(HMCLGameRepository repository, String instanceId) {
|
||||
this.repository = repository;
|
||||
this.instanceId = instanceId;
|
||||
|
||||
HMCLGameRepository repository = profile.getRepository();
|
||||
Version resolved = repository.getResolvedPreservingPatchesVersion(id);
|
||||
Version resolved = repository.getResolvedPreservingPatchesVersion(instanceId);
|
||||
this.gameVersion = repository.getGameVersion(resolved).orElse(null);
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(resolved, gameVersion);
|
||||
modded.set(analyzer.hasModLoader());
|
||||
loadMods(profile.getRepository().getModManager(id));
|
||||
loadMods(repository.getModManager(instanceId));
|
||||
}
|
||||
|
||||
private void loadMods(ModManager modManager) {
|
||||
@@ -232,14 +231,14 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
}
|
||||
|
||||
public void openModFolder() {
|
||||
FXUtils.openFolder(profile.getRepository().getRunDirectory(instanceId).resolve("mods"));
|
||||
FXUtils.openFolder(repository.getRunDirectory(instanceId).resolve("mods"));
|
||||
}
|
||||
|
||||
public void checkUpdates(Collection<LocalModFile> mods) {
|
||||
Objects.requireNonNull(mods);
|
||||
Runnable action = () -> Controllers.taskDialog(Task
|
||||
.composeAsync(() -> {
|
||||
Optional<String> gameVersion = profile.getRepository().getGameVersion(instanceId);
|
||||
Optional<String> gameVersion = repository.getGameVersion(instanceId);
|
||||
return gameVersion.map(g -> new AddonCheckUpdatesTask<>(DownloadProviders.getDownloadProvider(), g, mods)).orElse(null);
|
||||
})
|
||||
.whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
@@ -255,7 +254,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
.withStagesHints("update.checking"),
|
||||
i18n("addon.check_update"), TaskCancellationAction.NORMAL);
|
||||
|
||||
if (profile.getRepository().isModpack(instanceId)) {
|
||||
if (repository.isModpack(instanceId)) {
|
||||
Controllers.confirm(
|
||||
i18n("mods.update_modpack_mod.warning"), null,
|
||||
MessageDialogPane.MessageType.WARNING,
|
||||
@@ -291,8 +290,12 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
this.modded.set(modded);
|
||||
}
|
||||
|
||||
public Profile getProfile() {
|
||||
return this.profile;
|
||||
public GameDirectory getGameDirectory() {
|
||||
return this.repository.getGameDirectory();
|
||||
}
|
||||
|
||||
public HMCLGameRepository getRepository() {
|
||||
return this.repository;
|
||||
}
|
||||
|
||||
public String getInstanceId() {
|
||||
|
||||
@@ -42,8 +42,8 @@ import org.jackhuang.hmcl.addon.repository.CurseForgeRemoteAddonRepository;
|
||||
import org.jackhuang.hmcl.addon.mod.LocalModFile;
|
||||
import org.jackhuang.hmcl.addon.mod.ModLoaderType;
|
||||
import org.jackhuang.hmcl.addon.repository.ModrinthRemoteAddonRepository;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.setting.DownloadProviders;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.VersionIconType;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
@@ -502,7 +502,7 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
Controllers.navigate(new DownloadPage(
|
||||
repository instanceof CurseForgeRemoteAddonRepository ? HMCLLocalizedDownloadListPage.ofCurseForgeMod(null, false) : HMCLLocalizedDownloadListPage.ofModrinthMod(null, false),
|
||||
remoteAddon,
|
||||
new Profile.ProfileVersion(ModListPageSkin.this.getSkinnable().getProfile(), ModListPageSkin.this.getSkinnable().getInstanceId()),
|
||||
new HMCLGameRepository.InstanceReference(ModListPageSkin.this.getSkinnable().getRepository(), ModListPageSkin.this.getSkinnable().getInstanceId()),
|
||||
org.jackhuang.hmcl.ui.download.DownloadPage.FOR_MOD
|
||||
));
|
||||
});
|
||||
|
||||
@@ -42,9 +42,9 @@ import org.jackhuang.hmcl.addon.repository.CurseForgeRemoteAddonRepository;
|
||||
import org.jackhuang.hmcl.addon.repository.ModrinthRemoteAddonRepository;
|
||||
import org.jackhuang.hmcl.addon.resourcepack.ResourcePackFile;
|
||||
import org.jackhuang.hmcl.addon.resourcepack.ResourcePackManager;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.setting.SettingsManager;
|
||||
import org.jackhuang.hmcl.setting.DownloadProviders;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
@@ -74,7 +74,7 @@ import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class ResourcePackListPage extends ListPageBase<ResourcePackListPage.ResourcePackInfoObject> implements VersionPage.VersionLoadable {
|
||||
public final class ResourcePackListPage extends ListPageBase<ResourcePackListPage.ResourcePackInfoObject> implements VersionPage.GameInstanceLoadable {
|
||||
|
||||
private static final String TIP_KEY = "resourcePackWarning";
|
||||
private static @Nullable String getWarning(ResourcePackFile.Compatibility compatibility) {
|
||||
@@ -88,7 +88,7 @@ public final class ResourcePackListPage extends ListPageBase<ResourcePackListPag
|
||||
};
|
||||
}
|
||||
|
||||
private Profile profile;
|
||||
private HMCLGameRepository repository;
|
||||
private String instanceId;
|
||||
|
||||
private Path resourcePackDirectory;
|
||||
@@ -106,10 +106,10 @@ public final class ResourcePackListPage extends ListPageBase<ResourcePackListPag
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadVersion(Profile profile, String version) {
|
||||
this.profile = profile;
|
||||
this.instanceId = version;
|
||||
this.resourcePackManager = new ResourcePackManager(profile.getRepository(), version);
|
||||
public void loadInstance(HMCLGameRepository repository, String instanceId) {
|
||||
this.repository = repository;
|
||||
this.instanceId = instanceId;
|
||||
this.resourcePackManager = new ResourcePackManager(repository, instanceId);
|
||||
this.resourcePackDirectory = this.resourcePackManager.getDirectory();
|
||||
|
||||
refresh();
|
||||
@@ -232,7 +232,7 @@ public final class ResourcePackListPage extends ListPageBase<ResourcePackListPag
|
||||
public void checkUpdates(Collection<ResourcePackFile> resourcePacks) {
|
||||
Runnable action = () -> Controllers.taskDialog(Task
|
||||
.composeAsync(() -> {
|
||||
Optional<String> gameVersion = profile.getRepository().getGameVersion(instanceId);
|
||||
Optional<String> gameVersion = repository.getGameVersion(instanceId);
|
||||
return gameVersion.map(g -> new AddonCheckUpdatesTask<>(DownloadProviders.getDownloadProvider(), g, resourcePacks)).orElse(null);
|
||||
})
|
||||
.whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
@@ -247,7 +247,7 @@ public final class ResourcePackListPage extends ListPageBase<ResourcePackListPag
|
||||
.withStagesHints("update.checking"),
|
||||
i18n("addon.check_update"), TaskCancellationAction.NORMAL);
|
||||
|
||||
if (profile.getRepository().isModpack(instanceId)) {
|
||||
if (repository.isModpack(instanceId)) {
|
||||
Controllers.confirm(
|
||||
i18n("resourcepack.update_in_modpack.warning"), null,
|
||||
MessageDialogPane.MessageType.WARNING,
|
||||
@@ -619,7 +619,7 @@ public final class ResourcePackListPage extends ListPageBase<ResourcePackListPag
|
||||
? HMCLLocalizedDownloadListPage.ofCurseForgeResourcePack(null, false)
|
||||
: HMCLLocalizedDownloadListPage.ofModrinthResourcePack(null, false),
|
||||
remoteAddon,
|
||||
new Profile.ProfileVersion(page.profile, page.instanceId),
|
||||
new HMCLGameRepository.InstanceReference(page.repository, page.instanceId),
|
||||
org.jackhuang.hmcl.ui.download.DownloadPage.FOR_RESOURCE_PACK
|
||||
));
|
||||
});
|
||||
|
||||
@@ -34,8 +34,8 @@ import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.schematic.LitematicFile;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.*;
|
||||
@@ -62,7 +62,7 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class SchematicsPage extends ListPageBase<SchematicsPage.Item> implements VersionPage.VersionLoadable {
|
||||
public final class SchematicsPage extends ListPageBase<SchematicsPage.Item> implements VersionPage.GameInstanceLoadable {
|
||||
|
||||
private static String translateAuthorName(String author) {
|
||||
if (I18n.isUseChinese() && "hsds".equals(author)) {
|
||||
@@ -87,8 +87,8 @@ public final class SchematicsPage extends ListPageBase<SchematicsPage.Item> impl
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadVersion(Profile profile, String version) {
|
||||
this.schematicsDirectory = profile.getRepository().getSchematicsDirectory(version);
|
||||
public void loadInstance(HMCLGameRepository repository, String instanceId) {
|
||||
this.schematicsDirectory = repository.getSchematicsDirectory(instanceId);
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@ import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.event.Event;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.setting.GameSettings;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.VersionIconType;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
@@ -39,16 +39,16 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class VersionIconDialog extends DialogPane {
|
||||
private final Profile profile;
|
||||
private final HMCLGameRepository repository;
|
||||
private final String versionId;
|
||||
private final Runnable onFinish;
|
||||
private final GameSettings.Instance setting;
|
||||
|
||||
public VersionIconDialog(Profile profile, String versionId, Runnable onFinish) {
|
||||
this.profile = profile;
|
||||
public VersionIconDialog(HMCLGameRepository repository, String versionId, Runnable onFinish) {
|
||||
this.repository = repository;
|
||||
this.versionId = versionId;
|
||||
this.onFinish = onFinish;
|
||||
this.setting = profile.getRepository().getInstanceGameSettingsOrCreate(versionId);
|
||||
this.setting = repository.getInstanceGameSettingsOrCreate(versionId);
|
||||
|
||||
setTitle(i18n("settings.icon"));
|
||||
FlowPane pane = new FlowPane();
|
||||
@@ -79,7 +79,7 @@ public class VersionIconDialog extends DialogPane {
|
||||
Path selectedFile = FileUtils.toPath(chooser.showOpenDialog(Controllers.getStage()));
|
||||
if (selectedFile != null) {
|
||||
try {
|
||||
profile.getRepository().setVersionIconFile(versionId, selectedFile);
|
||||
repository.setVersionIconFile(versionId, selectedFile);
|
||||
|
||||
if (setting != null) {
|
||||
setting.iconProperty().setValue(VersionIconType.DEFAULT);
|
||||
@@ -119,7 +119,7 @@ public class VersionIconDialog extends DialogPane {
|
||||
|
||||
@Override
|
||||
protected void onAccept() {
|
||||
profile.getRepository().onVersionIconChanged.fireEvent(new Event(this));
|
||||
repository.onVersionIconChanged.fireEvent(new Event(this));
|
||||
onFinish.run();
|
||||
super.onAccept();
|
||||
}
|
||||
|
||||
@@ -29,9 +29,8 @@ import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.EventPriority;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
import org.jackhuang.hmcl.game.GameRepository;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.setting.GameSettings;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
@@ -45,6 +44,7 @@ import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.ui.game.GameSettingsPage;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
@@ -64,7 +64,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
private final TabHeader.Tab<ResourcePackListPage> resourcePackTab = new TabHeader.Tab<>("resourcePackTab");
|
||||
private final TransitionPane transitionPane = new TransitionPane();
|
||||
private final BooleanProperty currentVersionUpgradable = new SimpleBooleanProperty();
|
||||
private final ObjectProperty<Profile.ProfileVersion> version = new SimpleObjectProperty<>();
|
||||
private final ObjectProperty<HMCLGameRepository.InstanceReference> instanceReference = new SimpleObjectProperty<>();
|
||||
private final WeakListenerHolder listenerHolder = new WeakListenerHolder();
|
||||
|
||||
private String preferredVersionName = null;
|
||||
@@ -91,17 +91,17 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated);
|
||||
|
||||
addEventHandler(WorkingDirChangedEvent.EVENT_TYPE, event -> {
|
||||
if (this.version.get() != null) {
|
||||
if (this.instanceReference.get() != null) {
|
||||
if (installerListTab.isInitialized())
|
||||
installerListTab.getNode().loadVersion(getProfile(), getVersion());
|
||||
installerListTab.getNode().loadInstance(getRepository(), getVersion());
|
||||
if (modListTab.isInitialized())
|
||||
modListTab.getNode().loadVersion(getProfile(), getVersion());
|
||||
modListTab.getNode().loadInstance(getRepository(), getVersion());
|
||||
if (resourcePackTab.isInitialized())
|
||||
resourcePackTab.getNode().loadVersion(getProfile(), getVersion());
|
||||
resourcePackTab.getNode().loadInstance(getRepository(), getVersion());
|
||||
if (worldListTab.isInitialized())
|
||||
worldListTab.getNode().loadVersion(getProfile(), getVersion());
|
||||
worldListTab.getNode().loadInstance(getRepository(), getVersion());
|
||||
if (schematicsTab.isInitialized())
|
||||
schematicsTab.getNode().loadVersion(getProfile(), getVersion());
|
||||
schematicsTab.getNode().loadInstance(getRepository(), getVersion());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -110,11 +110,11 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
|
||||
private void checkSelectedVersion() {
|
||||
runInFX(() -> {
|
||||
if (this.version.get() == null) return;
|
||||
GameRepository repository = this.version.get().profile().getRepository();
|
||||
if (!repository.hasVersion(this.version.get().version())) {
|
||||
if (this.instanceReference.get() == null) return;
|
||||
HMCLGameRepository repository = this.instanceReference.get().repository();
|
||||
if (!repository.hasVersion(this.instanceReference.get().instanceId())) {
|
||||
if (preferredVersionName != null) {
|
||||
loadVersion(preferredVersionName, this.version.get().profile());
|
||||
loadVersion(preferredVersionName, repository);
|
||||
} else {
|
||||
fireEvent(new PageCloseEvent());
|
||||
}
|
||||
@@ -125,9 +125,9 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
private <T extends Node> Supplier<T> loadVersionFor(Supplier<T> nodeSupplier) {
|
||||
return () -> {
|
||||
T node = nodeSupplier.get();
|
||||
if (version.get() != null) {
|
||||
if (node instanceof VersionPage.VersionLoadable) {
|
||||
((VersionLoadable) node).loadVersion(version.get().profile(), version.get().version());
|
||||
if (instanceReference.get() != null) {
|
||||
if (node instanceof VersionPage.GameInstanceLoadable) {
|
||||
((GameInstanceLoadable) node).loadInstance(instanceReference.get().repository(), instanceReference.get().instanceId());
|
||||
}
|
||||
}
|
||||
return node;
|
||||
@@ -138,62 +138,62 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
tab.select(versionSettingsTab, false);
|
||||
}
|
||||
|
||||
public void setVersion(String version, Profile profile) {
|
||||
this.version.set(new Profile.ProfileVersion(profile, version));
|
||||
public void setVersion(String version, HMCLGameRepository repository) {
|
||||
this.instanceReference.set(new HMCLGameRepository.InstanceReference(repository, version));
|
||||
}
|
||||
|
||||
public void loadVersion(String version, Profile profile) {
|
||||
public void loadVersion(String version, HMCLGameRepository repository) {
|
||||
// If we jumped to game list page and deleted this version
|
||||
// and back to this page, we should return to main page.
|
||||
if (this.version.get() != null && (!getProfile().getRepository().isLoaded() ||
|
||||
!getProfile().getRepository().hasVersion(version))) {
|
||||
if (this.instanceReference.get() != null && (!getRepository().isLoaded() ||
|
||||
!getRepository().hasVersion(version))) {
|
||||
Platform.runLater(() -> fireEvent(new PageCloseEvent()));
|
||||
return;
|
||||
}
|
||||
|
||||
setVersion(version, profile);
|
||||
setVersion(version, repository);
|
||||
preferredVersionName = version;
|
||||
|
||||
if (versionSettingsTab.isInitialized())
|
||||
versionSettingsTab.getNode().loadVersion(profile, version);
|
||||
versionSettingsTab.getNode().loadInstance(repository, version);
|
||||
if (installerListTab.isInitialized())
|
||||
installerListTab.getNode().loadVersion(profile, version);
|
||||
installerListTab.getNode().loadInstance(repository, version);
|
||||
if (modListTab.isInitialized())
|
||||
modListTab.getNode().loadVersion(profile, version);
|
||||
modListTab.getNode().loadInstance(repository, version);
|
||||
if (resourcePackTab.isInitialized())
|
||||
resourcePackTab.getNode().loadVersion(profile, version);
|
||||
resourcePackTab.getNode().loadInstance(repository, version);
|
||||
if (worldListTab.isInitialized())
|
||||
worldListTab.getNode().loadVersion(profile, version);
|
||||
worldListTab.getNode().loadInstance(repository, version);
|
||||
if (schematicsTab.isInitialized())
|
||||
schematicsTab.getNode().loadVersion(profile, version);
|
||||
currentVersionUpgradable.set(profile.getRepository().isModpack(version));
|
||||
schematicsTab.getNode().loadInstance(repository, version);
|
||||
currentVersionUpgradable.set(repository.isModpack(version));
|
||||
}
|
||||
|
||||
private void onNavigated(Navigator.NavigationEvent event) {
|
||||
if (this.version.get() == null)
|
||||
if (this.instanceReference.get() == null)
|
||||
throw new IllegalStateException();
|
||||
|
||||
// If we jumped to game list page and deleted this version
|
||||
// and back to this page, we should return to main page.
|
||||
if (!getProfile().getRepository().isLoaded() ||
|
||||
!getProfile().getRepository().hasVersion(getVersion())) {
|
||||
if (!getRepository().isLoaded() ||
|
||||
!getRepository().hasVersion(getVersion())) {
|
||||
Platform.runLater(() -> fireEvent(new PageCloseEvent()));
|
||||
return;
|
||||
}
|
||||
|
||||
loadVersion(getVersion(), getProfile());
|
||||
loadVersion(getVersion(), getRepository());
|
||||
}
|
||||
|
||||
private void onBrowse(String sub) {
|
||||
FXUtils.openFolder(getProfile().getRepository().getRunDirectory(getVersion()).resolve(sub));
|
||||
FXUtils.openFolder(getRepository().getRunDirectory(getVersion()).resolve(sub));
|
||||
}
|
||||
|
||||
private void redownloadAssetIndex() {
|
||||
Versions.updateGameAssets(getProfile(), getVersion());
|
||||
Versions.updateGameAssets(getRepository(), getVersion());
|
||||
}
|
||||
|
||||
private void clearLibraries() {
|
||||
var libraries = getProfile().getRepository().getBaseDirectory().resolve("libraries");
|
||||
var libraries = getRepository().getBaseDirectory().resolve("libraries");
|
||||
Task.runAsync(Schedulers.io(), () -> {
|
||||
FileUtils.deleteDirectoryQuietly(libraries);
|
||||
}).whenComplete(Schedulers.javafx(), (exception) -> {
|
||||
@@ -204,11 +204,11 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
}
|
||||
|
||||
private void clearAssets() {
|
||||
Path assetsDir = getProfile().getRepository().getBaseDirectory().resolve("assets");
|
||||
Path assetsDir = getRepository().getBaseDirectory().resolve("assets");
|
||||
|
||||
Profile.ProfileVersion currentVersion = version.get();
|
||||
Path resourcesDir = currentVersion != null
|
||||
? getProfile().getRepository().getRunDirectory(currentVersion.version()).resolve("resources")
|
||||
HMCLGameRepository.InstanceReference currentInstanceReference = instanceReference.get();
|
||||
Path resourcesDir = currentInstanceReference != null
|
||||
? getRepository().getRunDirectory(currentInstanceReference.instanceId()).resolve("resources")
|
||||
: null;
|
||||
|
||||
Task.runAsync(Schedulers.io(), () -> {
|
||||
@@ -224,44 +224,44 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
}
|
||||
|
||||
private void clearJunkFiles() {
|
||||
Versions.cleanVersion(getProfile(), getVersion());
|
||||
Versions.cleanVersion(getRepository(), getVersion());
|
||||
}
|
||||
|
||||
private void testGame() {
|
||||
Versions.testGame(getProfile(), getVersion());
|
||||
Versions.testGame(getRepository(), getVersion());
|
||||
}
|
||||
|
||||
private void updateGame() {
|
||||
Versions.updateVersion(getProfile(), getVersion());
|
||||
Versions.updateVersion(getRepository(), getVersion());
|
||||
}
|
||||
|
||||
private void generateLaunchScript() {
|
||||
Versions.generateLaunchScript(getProfile(), getVersion());
|
||||
Versions.generateLaunchScript(getRepository(), getVersion());
|
||||
}
|
||||
|
||||
private void export() {
|
||||
Versions.exportVersion(getProfile(), getVersion());
|
||||
Versions.exportVersion(getRepository(), getVersion());
|
||||
}
|
||||
|
||||
private void rename() {
|
||||
Versions.renameVersion(getProfile(), getVersion())
|
||||
Versions.renameVersion(getRepository(), getVersion())
|
||||
.thenApply(newVersionName -> this.preferredVersionName = newVersionName);
|
||||
}
|
||||
|
||||
private void remove() {
|
||||
Versions.deleteVersion(getProfile(), getVersion());
|
||||
Versions.deleteVersion(getRepository(), getVersion());
|
||||
}
|
||||
|
||||
private void duplicate() {
|
||||
Versions.duplicateVersion(getProfile(), getVersion());
|
||||
Versions.duplicateVersion(getRepository(), getVersion());
|
||||
}
|
||||
|
||||
public Profile getProfile() {
|
||||
return Optional.ofNullable(version.get()).map(Profile.ProfileVersion::profile).orElse(null);
|
||||
public HMCLGameRepository getRepository() {
|
||||
return Optional.ofNullable(instanceReference.get()).map(HMCLGameRepository.InstanceReference::repository).orElse(null);
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return Optional.ofNullable(version.get()).map(Profile.ProfileVersion::version).orElse(null);
|
||||
return Optional.ofNullable(instanceReference.get()).map(HMCLGameRepository.InstanceReference::instanceId).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -345,7 +345,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
|
||||
control.state.bind(Bindings.createObjectBinding(() ->
|
||||
State.fromTitle(i18n("version.manage.manage.title", getSkinnable().getVersion()), -1),
|
||||
getSkinnable().version));
|
||||
getSkinnable().instanceReference));
|
||||
|
||||
//control.transitionPane.getStyleClass().add("gray-background");
|
||||
//FXUtils.setOverflowHidden(control.transitionPane, 8);
|
||||
@@ -353,7 +353,12 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
}
|
||||
}
|
||||
|
||||
public interface VersionLoadable {
|
||||
void loadVersion(Profile profile, String version);
|
||||
/// Loads page content for a game instance in a repository.
|
||||
public interface GameInstanceLoadable {
|
||||
/// Loads page content for the given repository and game instance.
|
||||
///
|
||||
/// @param repository the repository containing the game instance
|
||||
/// @param instanceId the game instance ID, or `null` when only repository context is available
|
||||
void loadInstance(HMCLGameRepository repository, @Nullable String instanceId);
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,8 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import org.jackhuang.hmcl.setting.GameDirectoryManager;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import javafx.application.Platform;
|
||||
import javafx.stage.FileChooser;
|
||||
@@ -70,13 +72,13 @@ public final class Versions {
|
||||
}
|
||||
|
||||
public static void importModpack() {
|
||||
Profile profile = Profiles.getSelectedProfile();
|
||||
if (profile.getRepository().isLoaded()) {
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(profile), i18n("install.modpack"));
|
||||
HMCLGameRepository repository = GameDirectoryManager.getSelectedRepository();
|
||||
if (repository.isLoaded()) {
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(repository), i18n("install.modpack"));
|
||||
}
|
||||
}
|
||||
|
||||
public static void downloadModpackImpl(DownloadProvider downloadProvider, Profile profile, String version, RemoteAddon mod, RemoteAddon.Version file) {
|
||||
public static void downloadModpackImpl(DownloadProvider downloadProvider, HMCLGameRepository repository, String version, RemoteAddon mod, RemoteAddon.Version file) {
|
||||
Path modpack;
|
||||
List<URI> downloadURLs;
|
||||
try {
|
||||
@@ -94,9 +96,9 @@ public final class Versions {
|
||||
if (e == null) {
|
||||
ModpackInstallWizardProvider installWizardProvider;
|
||||
if (version != null)
|
||||
installWizardProvider = new ModpackInstallWizardProvider(profile, modpack, version);
|
||||
installWizardProvider = new ModpackInstallWizardProvider(repository, modpack, version);
|
||||
else
|
||||
installWizardProvider = new ModpackInstallWizardProvider(profile, modpack);
|
||||
installWizardProvider = new ModpackInstallWizardProvider(repository, modpack);
|
||||
if (StringUtils.isNotBlank(mod.iconUrl()))
|
||||
installWizardProvider.setIconUrl(mod.iconUrl());
|
||||
Controllers.getDecorator().startWizard(installWizardProvider);
|
||||
@@ -113,16 +115,16 @@ public final class Versions {
|
||||
);
|
||||
}
|
||||
|
||||
public static void deleteVersion(Profile profile, String version) {
|
||||
boolean isIndependent = profile.getRepository().getRunDirectory(version).toAbsolutePath().normalize()
|
||||
.equals(profile.getRepository().getVersionRoot(version).toAbsolutePath().normalize());
|
||||
public static void deleteVersion(HMCLGameRepository repository, String version) {
|
||||
boolean isIndependent = repository.getRunDirectory(version).toAbsolutePath().normalize()
|
||||
.equals(repository.getVersionRoot(version).toAbsolutePath().normalize());
|
||||
String message = isIndependent ? i18n("version.manage.remove.confirm.independent", version) :
|
||||
i18n("version.manage.remove.confirm.trash", version, version + "_removed");
|
||||
|
||||
JFXButton deleteButton = new JFXButton(i18n("button.delete"));
|
||||
deleteButton.getStyleClass().add("dialog-error");
|
||||
deleteButton.setOnAction(e -> {
|
||||
Task.supplyAsync(Schedulers.io(), () -> profile.getRepository().removeVersionFromDisk(version))
|
||||
Task.supplyAsync(Schedulers.io(), () -> repository.removeVersionFromDisk(version))
|
||||
.whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
if (exception != null || !Boolean.TRUE.equals(result)) {
|
||||
Controllers.dialog(i18n("version.manage.remove.failed"), i18n("message.error"), MessageDialogPane.MessageType.ERROR);
|
||||
@@ -133,18 +135,18 @@ public final class Versions {
|
||||
Controllers.confirmAction(message, i18n("message.warning"), MessageDialogPane.MessageType.WARNING, deleteButton);
|
||||
}
|
||||
|
||||
public static CompletableFuture<String> renameVersion(Profile profile, String version) {
|
||||
public static CompletableFuture<String> renameVersion(HMCLGameRepository repository, String version) {
|
||||
return Controllers.prompt(i18n("version.manage.rename.message"), (newName, handler) -> {
|
||||
if (newName.equals(version)) {
|
||||
handler.resolve();
|
||||
return;
|
||||
}
|
||||
if (profile.getRepository().renameVersion(version, newName)) {
|
||||
if (repository.renameVersion(version, newName)) {
|
||||
handler.resolve();
|
||||
profile.getRepository().refreshVersionsAsync()
|
||||
repository.refreshVersionsAsync()
|
||||
.thenRunAsync(Schedulers.javafx(), () -> {
|
||||
if (profile.getRepository().hasVersion(newName)) {
|
||||
Profiles.setSelectedInstance(profile, newName);
|
||||
if (repository.hasVersion(newName)) {
|
||||
repository.setSelectedInstance(newName);
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
@@ -152,21 +154,21 @@ public final class Versions {
|
||||
}
|
||||
}, version,
|
||||
new Validator(i18n("install.new_game.malformed"), HMCLGameRepository::isValidVersionId),
|
||||
new Validator(i18n("install.new_game.already_exists"), newVersionName -> !profile.getRepository().versionIdConflicts(newVersionName) || newVersionName.equals(version)));
|
||||
new Validator(i18n("install.new_game.already_exists"), newVersionName -> !repository.versionIdConflicts(newVersionName) || newVersionName.equals(version)));
|
||||
}
|
||||
|
||||
public static void exportVersion(Profile profile, String version) {
|
||||
Controllers.getDecorator().startWizard(new ExportWizardProvider(profile, version), i18n("modpack.wizard"));
|
||||
public static void exportVersion(HMCLGameRepository repository, String version) {
|
||||
Controllers.getDecorator().startWizard(new ExportWizardProvider(repository, version), i18n("modpack.wizard"));
|
||||
}
|
||||
|
||||
public static void openFolder(Profile profile, String version) {
|
||||
FXUtils.openFolder(profile.getRepository().getRunDirectory(version));
|
||||
public static void openFolder(HMCLGameRepository repository, String version) {
|
||||
FXUtils.openFolder(repository.getRunDirectory(version));
|
||||
}
|
||||
|
||||
public static void installFromJson(Profile profile, Path file) {
|
||||
public static void installFromJson(HMCLGameRepository repository, Path file) {
|
||||
Version version;
|
||||
try {
|
||||
version = profile.getRepository().readVersionJson(file);
|
||||
version = repository.readVersionJson(file);
|
||||
} catch (Exception e) {
|
||||
Controllers.dialog(i18n("install.new_game.malformed_json"), i18n("message.error"), MessageDialogPane.MessageType.ERROR);
|
||||
return;
|
||||
@@ -175,8 +177,7 @@ public final class Versions {
|
||||
Controllers.prompt(i18n("version.manage.duplicate.prompt"), (result, handler) -> {
|
||||
handler.resolve();
|
||||
|
||||
DefaultDependencyManager dependencyManager = profile.getDependency();
|
||||
HMCLGameRepository repository = profile.getRepository();
|
||||
DefaultDependencyManager dependencyManager = repository.getDependency();
|
||||
Version newVersion = version.setId(result).setJar(result);
|
||||
|
||||
Controllers.taskDialog(
|
||||
@@ -191,29 +192,29 @@ public final class Versions {
|
||||
.thenRunAsync(repository::refreshVersions)
|
||||
.whenComplete(Schedulers.javafx(), (exception) -> {
|
||||
if (exception == null) {
|
||||
Profiles.setSelectedInstance(profile, result);
|
||||
repository.setSelectedInstance(result);
|
||||
} else {
|
||||
Controllers.dialog(
|
||||
DownloadProviders.localizeErrorMessage(exception), i18n("install.failed"), MessageDialogPane.MessageType.ERROR);
|
||||
}
|
||||
}), i18n("install.new_game"), TaskCancellationAction.NORMAL);
|
||||
}, FileUtils.getNameWithoutExtension(file), new Validator(i18n("install.new_game.malformed"), HMCLGameRepository::isValidVersionId), new Validator(i18n("install.new_game.already_exists"), newVersionName -> !profile.getRepository().versionIdConflicts(newVersionName)));
|
||||
}, FileUtils.getNameWithoutExtension(file), new Validator(i18n("install.new_game.malformed"), HMCLGameRepository::isValidVersionId), new Validator(i18n("install.new_game.already_exists"), newVersionName -> !repository.versionIdConflicts(newVersionName)));
|
||||
}
|
||||
|
||||
public static void duplicateVersion(Profile profile, String version) {
|
||||
public static void duplicateVersion(HMCLGameRepository repository, String version) {
|
||||
Controllers.prompt(
|
||||
new PromptDialogPane.Builder(i18n("version.manage.duplicate.prompt"), (res, handler) -> {
|
||||
String newVersionName = ((PromptDialogPane.Builder.StringQuestion) res.get(1)).getValue();
|
||||
boolean copySaves = ((PromptDialogPane.Builder.BooleanQuestion) res.get(2)).getValue();
|
||||
Task.runAsync(() -> profile.getRepository().duplicateVersion(version, newVersionName, copySaves))
|
||||
.thenComposeAsync(profile.getRepository().refreshVersionsAsync())
|
||||
Task.runAsync(() -> repository.duplicateVersion(version, newVersionName, copySaves))
|
||||
.thenComposeAsync(repository.refreshVersionsAsync())
|
||||
.whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
if (exception == null) {
|
||||
handler.resolve();
|
||||
} else {
|
||||
handler.reject(StringUtils.getStackTrace(exception));
|
||||
if (!profile.getRepository().versionIdConflicts(newVersionName)) {
|
||||
profile.getRepository().removeVersionFromDisk(newVersionName);
|
||||
if (!repository.versionIdConflicts(newVersionName)) {
|
||||
repository.removeVersionFromDisk(newVersionName);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
@@ -221,35 +222,34 @@ public final class Versions {
|
||||
.addQuestion(new PromptDialogPane.Builder.HintQuestion(i18n("version.manage.duplicate.confirm")))
|
||||
.addQuestion(new PromptDialogPane.Builder.StringQuestion(null, version,
|
||||
new Validator(i18n("install.new_game.malformed"), HMCLGameRepository::isValidVersionId),
|
||||
new Validator(i18n("install.new_game.already_exists"), newVersionName -> !profile.getRepository().versionIdConflicts(newVersionName))))
|
||||
new Validator(i18n("install.new_game.already_exists"), newVersionName -> !repository.versionIdConflicts(newVersionName))))
|
||||
.addQuestion(new PromptDialogPane.Builder.BooleanQuestion(i18n("version.manage.duplicate.duplicate_save"), false)));
|
||||
}
|
||||
|
||||
public static void updateVersion(Profile profile, String version) {
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(profile, version));
|
||||
public static void updateVersion(HMCLGameRepository repository, String version) {
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(repository, version));
|
||||
}
|
||||
|
||||
public static void updateGameAssets(Profile profile, String version) {
|
||||
TaskExecutor executor = new GameAssetDownloadTask(profile.getDependency(), profile.getRepository().getVersion(version), GameAssetDownloadTask.DOWNLOAD_INDEX_FORCIBLY, true)
|
||||
public static void updateGameAssets(HMCLGameRepository repository, String version) {
|
||||
TaskExecutor executor = new GameAssetDownloadTask(repository.getDependency(), repository.getVersion(version), GameAssetDownloadTask.DOWNLOAD_INDEX_FORCIBLY, true)
|
||||
.executor();
|
||||
Controllers.taskDialog(executor, i18n("version.manage.redownload_assets_index"), TaskCancellationAction.NO_CANCEL);
|
||||
executor.start();
|
||||
}
|
||||
|
||||
public static void cleanVersion(Profile profile, String id) {
|
||||
public static void cleanVersion(HMCLGameRepository repository, String id) {
|
||||
try {
|
||||
profile.getRepository().clean(id);
|
||||
repository.clean(id);
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Unable to clean game directory", e);
|
||||
}
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static void generateLaunchScript(Profile profile, String id, Consumer<LauncherHelper>... injecters) {
|
||||
if (!checkVersionForLaunching(profile, id))
|
||||
public static void generateLaunchScript(HMCLGameRepository repository, String id, Consumer<LauncherHelper>... injecters) {
|
||||
if (!checkVersionForLaunching(repository, id))
|
||||
return;
|
||||
ensureSelectedAccount(account -> {
|
||||
GameRepository repository = profile.getRepository();
|
||||
FileChooser chooser = new FileChooser();
|
||||
if (Files.isDirectory(repository.getRunDirectory(id)))
|
||||
chooser.setInitialDirectory(repository.getRunDirectory(id).toFile());
|
||||
@@ -270,7 +270,7 @@ public final class Versions {
|
||||
file = file.resolveSibling(file.getFileName().toString() + "." + defaultExt);
|
||||
}
|
||||
|
||||
LauncherHelper launcherHelper = new LauncherHelper(profile, account, id);
|
||||
LauncherHelper launcherHelper = new LauncherHelper(repository, account, id);
|
||||
for (Consumer<LauncherHelper> injecter : injecters) {
|
||||
injecter.accept(launcherHelper);
|
||||
}
|
||||
@@ -295,11 +295,11 @@ public final class Versions {
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static void launch(Profile profile, String id, Consumer<LauncherHelper>... injecters) {
|
||||
if (!checkVersionForLaunching(profile, id))
|
||||
public static void launch(HMCLGameRepository repository, String id, Consumer<LauncherHelper>... injecters) {
|
||||
if (!checkVersionForLaunching(repository, id))
|
||||
return;
|
||||
ensureSelectedAccount(account -> {
|
||||
LauncherHelper launcherHelper = new LauncherHelper(profile, account, id);
|
||||
LauncherHelper launcherHelper = new LauncherHelper(repository, account, id);
|
||||
for (Consumer<LauncherHelper> injecter : injecters) {
|
||||
injecter.accept(launcherHelper);
|
||||
}
|
||||
@@ -307,22 +307,22 @@ public final class Versions {
|
||||
});
|
||||
}
|
||||
|
||||
public static void testGame(Profile profile, String id) {
|
||||
launch(profile, id, LauncherHelper::setTestMode);
|
||||
public static void testGame(HMCLGameRepository repository, String id) {
|
||||
launch(repository, id, LauncherHelper::setTestMode);
|
||||
}
|
||||
|
||||
public static void launchAndEnterWorld(Profile profile, String id, String worldFolderName) {
|
||||
launch(profile, id, launcherHelper ->
|
||||
public static void launchAndEnterWorld(HMCLGameRepository repository, String id, String worldFolderName) {
|
||||
launch(repository, id, launcherHelper ->
|
||||
launcherHelper.setQuickPlayOption(new QuickPlayOption.SinglePlayer(worldFolderName)));
|
||||
}
|
||||
|
||||
public static void generateLaunchScriptForQuickEnterWorld(Profile profile, String id, String worldFolderName) {
|
||||
generateLaunchScript(profile, id, launcherHelper ->
|
||||
public static void generateLaunchScriptForQuickEnterWorld(HMCLGameRepository repository, String id, String worldFolderName) {
|
||||
generateLaunchScript(repository, id, launcherHelper ->
|
||||
launcherHelper.setQuickPlayOption(new QuickPlayOption.SinglePlayer(worldFolderName)));
|
||||
}
|
||||
|
||||
private static boolean checkVersionForLaunching(Profile profile, String id) {
|
||||
if (id == null || !profile.getRepository().isLoaded() || !profile.getRepository().hasVersion(id)) {
|
||||
private static boolean checkVersionForLaunching(HMCLGameRepository repository, String id) {
|
||||
if (id == null || !repository.isLoaded() || !repository.hasVersion(id)) {
|
||||
JFXButton gotoDownload = new JFXButton(i18n("version.empty.launch.goto_download"));
|
||||
gotoDownload.getStyleClass().add("dialog-accept");
|
||||
gotoDownload.setOnAction(e -> Controllers.navigate(Controllers.getDownloadPage()));
|
||||
@@ -367,13 +367,13 @@ public final class Versions {
|
||||
}
|
||||
}
|
||||
|
||||
public static void modifyGlobalSettings(Profile profile) {
|
||||
Controllers.getSettingsPage().showGameSettings(profile);
|
||||
public static void modifyGlobalSettings(HMCLGameRepository repository) {
|
||||
Controllers.getSettingsPage().showGameSettings(repository);
|
||||
Controllers.navigate(Controllers.getSettingsPage());
|
||||
}
|
||||
|
||||
public static void modifyGameSettings(Profile profile, String version) {
|
||||
Controllers.getVersionPage().setVersion(version, profile);
|
||||
public static void modifyGameSettings(HMCLGameRepository repository, String version) {
|
||||
Controllers.getVersionPage().setVersion(version, repository);
|
||||
Controllers.getVersionPage().showInstanceSettings();
|
||||
// VersionPage.loadVersion will be invoked after navigation
|
||||
Controllers.navigate(Controllers.getVersionPage());
|
||||
|
||||
@@ -35,8 +35,8 @@ import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
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.*;
|
||||
@@ -61,12 +61,12 @@ import static org.jackhuang.hmcl.util.i18n.I18n.formatDateTime;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class WorldListPage extends ListPageBase<World> implements VersionPage.VersionLoadable {
|
||||
public final class WorldListPage extends ListPageBase<World> implements VersionPage.GameInstanceLoadable {
|
||||
private final BooleanProperty showAll = new SimpleBooleanProperty(this, "showAll", false);
|
||||
|
||||
private Path savesDir;
|
||||
private List<World> worlds;
|
||||
private Profile profile;
|
||||
private HMCLGameRepository repository;
|
||||
private String instanceId;
|
||||
private final BooleanProperty supportQuickPlay = new SimpleBooleanProperty(this, "supportQuickPlay", false);
|
||||
|
||||
@@ -86,10 +86,10 @@ public final class WorldListPage extends ListPageBase<World> implements VersionP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadVersion(Profile profile, String id) {
|
||||
this.profile = profile;
|
||||
this.instanceId = id;
|
||||
this.savesDir = profile.getRepository().getSavesDirectory(id);
|
||||
public void loadInstance(HMCLGameRepository repository, String instanceId) {
|
||||
this.repository = repository;
|
||||
this.instanceId = instanceId;
|
||||
this.savesDir = repository.getSavesDirectory(instanceId);
|
||||
refresh();
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ public final class WorldListPage extends ListPageBase<World> implements VersionP
|
||||
} else if (showAll.get()) {
|
||||
getItems().setAll(worlds);
|
||||
} else {
|
||||
GameVersionNumber gameVersion = profile.getRepository().getGameVersion(instanceId).map(GameVersionNumber::asGameVersion).orElse(null);
|
||||
GameVersionNumber gameVersion = repository.getGameVersion(instanceId).map(GameVersionNumber::asGameVersion).orElse(null);
|
||||
getItems().setAll(worlds.stream()
|
||||
.filter(world -> world.getGameVersion() == null || world.getGameVersion().equals(gameVersion))
|
||||
.toList());
|
||||
@@ -107,7 +107,7 @@ public final class WorldListPage extends ListPageBase<World> implements VersionP
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
if (profile == null || instanceId == null)
|
||||
if (repository == null || instanceId == null)
|
||||
return;
|
||||
|
||||
int currentRefresh = ++refreshCount;
|
||||
@@ -115,7 +115,7 @@ public final class WorldListPage extends ListPageBase<World> implements VersionP
|
||||
setLoading(true);
|
||||
Task.supplyAsync(Schedulers.io(), () -> {
|
||||
// Ensure the game version number is parsed
|
||||
profile.getRepository().getGameVersion(instanceId);
|
||||
repository.getGameVersion(instanceId);
|
||||
return World.getWorlds(savesDir);
|
||||
}).whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
if (refreshCount != currentRefresh) {
|
||||
@@ -123,7 +123,7 @@ public final class WorldListPage extends ListPageBase<World> implements VersionP
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<String> gameVersion = profile.getRepository().getGameVersion(instanceId);
|
||||
Optional<String> gameVersion = repository.getGameVersion(instanceId);
|
||||
supportQuickPlay.set(World.supportQuickPlay(GameVersionNumber.asGameVersion(gameVersion)));
|
||||
|
||||
worlds = result;
|
||||
@@ -177,7 +177,7 @@ public final class WorldListPage extends ListPageBase<World> implements VersionP
|
||||
}
|
||||
|
||||
private void showManagePage(World world) {
|
||||
Controllers.navigate(new WorldManagePage(world, profile, instanceId));
|
||||
Controllers.navigate(new WorldManagePage(world, repository, instanceId));
|
||||
}
|
||||
|
||||
public void export(World world) {
|
||||
@@ -197,11 +197,11 @@ public final class WorldListPage extends ListPageBase<World> implements VersionP
|
||||
}
|
||||
|
||||
public void launch(World world) {
|
||||
Versions.launchAndEnterWorld(profile, instanceId, world.getFileName());
|
||||
Versions.launchAndEnterWorld(repository, instanceId, world.getFileName());
|
||||
}
|
||||
|
||||
public void generateLaunchScript(World world) {
|
||||
Versions.generateLaunchScriptForQuickEnterWorld(profile, instanceId, world.getFileName());
|
||||
Versions.generateLaunchScriptForQuickEnterWorld(repository, instanceId, world.getFileName());
|
||||
}
|
||||
|
||||
public BooleanProperty showAllProperty() {
|
||||
|
||||
@@ -24,8 +24,8 @@ import javafx.geometry.Insets;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.World;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
@@ -53,7 +53,7 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco
|
||||
|
||||
private final World world;
|
||||
private final Path backupsDir;
|
||||
private final Profile profile;
|
||||
private final HMCLGameRepository repository;
|
||||
private final String instanceId;
|
||||
private final boolean supportQuickPlay;
|
||||
private FileChannel sessionLockChannel;
|
||||
@@ -69,10 +69,10 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco
|
||||
private final TabHeader.Tab<WorldBackupsPage> worldBackupsTab = new TabHeader.Tab<>("worldBackupsPage");
|
||||
private final TabHeader.Tab<DataPackListPage> dataPackTab = new TabHeader.Tab<>("dataPackListPage");
|
||||
|
||||
public WorldManagePage(World world, Profile profile, String instanceId) {
|
||||
public WorldManagePage(World world, HMCLGameRepository repository, String instanceId) {
|
||||
this.world = world;
|
||||
this.backupsDir = profile.getRepository().getBackupsDirectory(instanceId);
|
||||
this.profile = profile;
|
||||
this.backupsDir = repository.getBackupsDirectory(instanceId);
|
||||
this.repository = repository;
|
||||
this.instanceId = instanceId;
|
||||
|
||||
updateSessionLockChannel();
|
||||
@@ -90,7 +90,7 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco
|
||||
|
||||
this.state = new SimpleObjectProperty<>(new State(i18n("world.manage.title", StringUtils.parseColorEscapes(world.getWorldName())), null, true, true, true));
|
||||
|
||||
Optional<String> gameVersion = profile.getRepository().getGameVersion(instanceId);
|
||||
Optional<String> gameVersion = repository.getGameVersion(instanceId);
|
||||
supportQuickPlay = World.supportQuickPlay(GameVersionNumber.asGameVersion(gameVersion));
|
||||
|
||||
this.addEventHandler(Navigator.NavigationEvent.EXITED, this::onExited);
|
||||
@@ -150,11 +150,11 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco
|
||||
|
||||
public void launch() {
|
||||
fireEvent(new PageCloseEvent());
|
||||
Versions.launchAndEnterWorld(profile, instanceId, world.getFileName());
|
||||
Versions.launchAndEnterWorld(repository, instanceId, world.getFileName());
|
||||
}
|
||||
|
||||
public void generateLaunchScript() {
|
||||
Versions.generateLaunchScriptForQuickEnterWorld(profile, instanceId, world.getFileName());
|
||||
Versions.generateLaunchScriptForQuickEnterWorld(repository, instanceId, world.getFileName());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -210,28 +210,28 @@
|
||||
-fx-padding: 0 0 0 0;
|
||||
}
|
||||
|
||||
.profile-list-item > .rippler-container > BorderPane > .two-line-list-item > .first-line > .title {
|
||||
.game-directory-list-item > .rippler-container > BorderPane > .two-line-list-item > .first-line > .title {
|
||||
-fx-font-size: 13;
|
||||
}
|
||||
|
||||
.profile-list-item > .rippler-container > BorderPane > .two-line-list-item > HBox > .subtitle {
|
||||
.game-directory-list-item > .rippler-container > BorderPane > .two-line-list-item > HBox > .subtitle {
|
||||
-fx-font-size: 10;
|
||||
}
|
||||
|
||||
.profile-list-item > .rippler-container > BorderPane {
|
||||
.game-directory-list-item > .rippler-container > BorderPane {
|
||||
-fx-padding: 8 0 8 16;
|
||||
}
|
||||
|
||||
.profile-list-item:selected > .rippler-container > BorderPane {
|
||||
.game-directory-list-item:selected > .rippler-container > BorderPane {
|
||||
-fx-background-color: -monet-secondary-container-transparent-50;
|
||||
}
|
||||
|
||||
.profile-list-item:selected > .rippler-container > BorderPane > .two-line-list-item > .first-line > .title {
|
||||
.game-directory-list-item:selected > .rippler-container > BorderPane > .two-line-list-item > .first-line > .title {
|
||||
-fx-text-fill: -monet-on-secondary-container;
|
||||
-fx-font-weight: bold;
|
||||
}
|
||||
|
||||
.profile-list-item:selected .svg {
|
||||
.game-directory-list-item:selected .svg {
|
||||
-fx-fill: -monet-on-secondary-container;
|
||||
}
|
||||
|
||||
|
||||
@@ -742,6 +742,19 @@ game.crash.title=Game Crashed
|
||||
game.directory=Game Path
|
||||
game.version=Game Instance
|
||||
|
||||
game_directory=Game Directories
|
||||
game_directory.already_exists=This name already exists. Please use a different name.
|
||||
game_directory.default=Current
|
||||
game_directory.home=Minecraft Launcher
|
||||
game_directory.instance_directory=Game Directory
|
||||
game_directory.instance_directory.choose=Choose game directory
|
||||
game_directory.manage=Instance Directory List
|
||||
game_directory.name=Name
|
||||
game_directory.new=New Directory
|
||||
game_directory.title=Game Directories
|
||||
game_directory.selected=Selected
|
||||
game_directory.use_relative_path=Use a relative path for the game directory if possible
|
||||
|
||||
help=Help
|
||||
help.doc=Hello Minecraft! Launcher Documentation
|
||||
help.detail=For data pack and modpack makers.
|
||||
@@ -1264,19 +1277,6 @@ world.name=World Name
|
||||
world.name.enter=Enter the world name
|
||||
world.show_all=Show All
|
||||
|
||||
profile=Game Directories
|
||||
profile.already_exists=This name already exists. Please use a different name.
|
||||
profile.default=Current
|
||||
profile.home=Minecraft Launcher
|
||||
profile.instance_directory=Game Directory
|
||||
profile.instance_directory.choose=Choose game directory
|
||||
profile.manage=Instance Directory List
|
||||
profile.name=Name
|
||||
profile.new=New Directory
|
||||
profile.title=Game Directories
|
||||
profile.selected=Selected
|
||||
profile.use_relative_path=Use a relative path for the game directory if possible
|
||||
|
||||
repositories.custom=Custom Maven Repository (%s)
|
||||
repositories.maven_central=Universal (Maven Central)
|
||||
repositories.tencentcloud_mirror=Chinese Mainland Mirror (Tencent Cloud Maven Repository)
|
||||
|
||||
@@ -706,6 +706,19 @@ game.crash.title=تعطّلت اللعبة
|
||||
game.directory=مسار اللعبة
|
||||
game.version=نسخة اللعبة
|
||||
|
||||
game_directory=مجلدات اللعبة
|
||||
game_directory.already_exists=هذا الاسم موجود مسبقاً. يُرجى استخدام اسم مختلف.
|
||||
game_directory.default=الحالي
|
||||
game_directory.home=مشغّل Minecraft
|
||||
game_directory.instance_directory=مجلد اللعبة
|
||||
game_directory.instance_directory.choose=اختر مجلد اللعبة
|
||||
game_directory.manage=قائمة مجلدات النسخ
|
||||
game_directory.name=الاسم
|
||||
game_directory.new=مجلد جديد
|
||||
game_directory.title=مجلدات اللعبة
|
||||
game_directory.selected=محدد
|
||||
game_directory.use_relative_path=استخدام مسار نسبي لمجلد اللعبة إن أمكن
|
||||
|
||||
help=مساعدة
|
||||
help.doc=توثيق Hello Minecraft! Launcher
|
||||
help.detail=لصانعي حزم البيانات وحزم المودات.
|
||||
@@ -1208,19 +1221,6 @@ world.name=اسم العالم
|
||||
world.name.enter=أدخل اسم العالم
|
||||
world.show_all=عرض الكل
|
||||
|
||||
profile=مجلدات اللعبة
|
||||
profile.already_exists=هذا الاسم موجود مسبقاً. يُرجى استخدام اسم مختلف.
|
||||
profile.default=الحالي
|
||||
profile.home=مشغّل Minecraft
|
||||
profile.instance_directory=مجلد اللعبة
|
||||
profile.instance_directory.choose=اختر مجلد اللعبة
|
||||
profile.manage=قائمة مجلدات النسخ
|
||||
profile.name=الاسم
|
||||
profile.new=مجلد جديد
|
||||
profile.title=مجلدات اللعبة
|
||||
profile.selected=محدد
|
||||
profile.use_relative_path=استخدام مسار نسبي لمجلد اللعبة إن أمكن
|
||||
|
||||
repositories.custom=مستودع Maven مخصص (%s)
|
||||
repositories.maven_central=عالمي (Maven Central)
|
||||
repositories.tencentcloud_mirror=مرآة البر الرئيسي الصيني (مستودع Tencent Cloud Maven)
|
||||
|
||||
@@ -672,6 +672,19 @@ game.crash.title=Juego colgado
|
||||
game.directory=Ruta del juego
|
||||
game.version=Instancia del juego
|
||||
|
||||
game_directory=Directorios del juego
|
||||
game_directory.already_exists=Este nombre ya existe, por favor, utilice un nombre diferente.
|
||||
game_directory.default=Directorio actual
|
||||
game_directory.home=Minecraft Launcher
|
||||
game_directory.instance_directory=Directorio ded juego
|
||||
game_directory.instance_directory.choose=Elegir directorio de juegos
|
||||
game_directory.manage=Lista de directorios de instancia
|
||||
game_directory.name=Nombre
|
||||
game_directory.new=Nuevo directorio
|
||||
game_directory.title=Directorios del juego
|
||||
game_directory.selected=Seleccionado
|
||||
game_directory.use_relative_path=Utilizar ruta relativa para la ruta del juego si es posible
|
||||
|
||||
help=Ayuda
|
||||
help.doc=Documentación de Hello Minecraft! Launcher
|
||||
help.detail=Para los creadores de paquetes de datos y mods.
|
||||
@@ -1131,19 +1144,6 @@ world.name=Nombre del mundo
|
||||
world.name.enter=Introducir el nombre del mundo
|
||||
world.show_all=Mostrar todo
|
||||
|
||||
profile=Directorios del juego
|
||||
profile.already_exists=Este nombre ya existe, por favor, utilice un nombre diferente.
|
||||
profile.default=Directorio actual
|
||||
profile.home=Minecraft Launcher
|
||||
profile.instance_directory=Directorio ded juego
|
||||
profile.instance_directory.choose=Elegir directorio de juegos
|
||||
profile.manage=Lista de directorios de instancia
|
||||
profile.name=Nombre
|
||||
profile.new=Nuevo directorio
|
||||
profile.title=Directorios del juego
|
||||
profile.selected=Seleccionado
|
||||
profile.use_relative_path=Utilizar ruta relativa para la ruta del juego si es posible
|
||||
|
||||
repositories.custom=Repositorio Maven personalizado (%s)
|
||||
repositories.maven_central=Universal (Maven Central)
|
||||
repositories.tencentcloud_mirror=Espejo de China continental (Repositorio Maven Tencent Cloud)
|
||||
|
||||
@@ -414,6 +414,19 @@ game.crash.title=ゲームがクラッシュしました
|
||||
game.directory=ゲームディレクトリ
|
||||
game.version=ゲームバージョン
|
||||
|
||||
game_directory=ゲームディレクトリ
|
||||
game_directory.already_exists=この名前はすでに存在します。別の名前を使用してください。
|
||||
game_directory.default=現在のディレクトリ
|
||||
game_directory.home=標準ディレクトリ
|
||||
game_directory.instance_directory=ゲームディレクトリ
|
||||
game_directory.instance_directory.choose=ゲームディレクトリを選択してください
|
||||
game_directory.manage=ゲームディレクトリリスト
|
||||
game_directory.name=名前
|
||||
game_directory.new=新規作成
|
||||
game_directory.title=ゲームディレクトリ
|
||||
game_directory.selected=選択済み
|
||||
game_directory.use_relative_path=可能であれば、ゲームディレクトリの相対パスを使用します
|
||||
|
||||
help=ヘルプ
|
||||
help.doc=ドキュメント
|
||||
help.detail=データパック、modpackなどのメーカー向け。
|
||||
@@ -703,19 +716,6 @@ world.name=マップ名
|
||||
world.name.enter=マップ名を入力してください
|
||||
world.show_all=すべて表示
|
||||
|
||||
profile=ゲームディレクトリ
|
||||
profile.already_exists=この名前はすでに存在します。別の名前を使用してください。
|
||||
profile.default=現在のディレクトリ
|
||||
profile.home=標準ディレクトリ
|
||||
profile.instance_directory=ゲームディレクトリ
|
||||
profile.instance_directory.choose=ゲームディレクトリを選択してください
|
||||
profile.manage=ゲームディレクトリリスト
|
||||
profile.name=名前
|
||||
profile.new=新規作成
|
||||
profile.title=ゲームディレクトリ
|
||||
profile.selected=選択済み
|
||||
profile.use_relative_path=可能であれば、ゲームディレクトリの相対パスを使用します
|
||||
|
||||
repositories.custom=カスタムMavenリポジトリ(%s)
|
||||
repositories.maven_central=Universal(Maven Central)
|
||||
repositories.tencentcloud_mirror=中国本土(Tencent Cloud Mavenリポジトリ)
|
||||
|
||||
@@ -488,6 +488,19 @@ game.crash.title=不期而退
|
||||
game.directory=戲案夾之径
|
||||
game.version=戲例
|
||||
|
||||
game_directory=戲案夾
|
||||
game_directory.already_exists=名既存
|
||||
game_directory.default=當案夾
|
||||
game_directory.home=官之啟者置設文之案夾
|
||||
game_directory.instance_directory=戲案夾之徑
|
||||
game_directory.instance_directory.choose=擇戲案夾之徑
|
||||
game_directory.manage=戲案夾列
|
||||
game_directory.name=名
|
||||
game_directory.new=另增戲案夾
|
||||
game_directory.title=戲案夾
|
||||
game_directory.selected=既擇
|
||||
game_directory.use_relative_path=苟能是,戲案夾用相對之徑
|
||||
|
||||
help=助
|
||||
help.detail=所以閲錄囊、改囊集之司南與他者
|
||||
help.doc=Hello Minecraft! Launcher 之案文
|
||||
@@ -946,19 +959,6 @@ world.name=生界之名
|
||||
world.name.enter=書生界之名
|
||||
world.show_all=全顯
|
||||
|
||||
profile=戲案夾
|
||||
profile.already_exists=名既存
|
||||
profile.default=當案夾
|
||||
profile.home=官之啟者置設文之案夾
|
||||
profile.instance_directory=戲案夾之徑
|
||||
profile.instance_directory.choose=擇戲案夾之徑
|
||||
profile.manage=戲案夾列
|
||||
profile.name=名
|
||||
profile.new=另增戲案夾
|
||||
profile.title=戲案夾
|
||||
profile.selected=既擇
|
||||
profile.use_relative_path=苟能是,戲案夾用相對之徑
|
||||
|
||||
repositories.custom=自定 Maven 倉 (%s)
|
||||
repositories.maven_central=天下之倉 (Maven Central)
|
||||
repositories.tencentcloud_mirror=本朝 (騰訊云轉寫 Maven 倉儲)
|
||||
|
||||
@@ -664,6 +664,19 @@ game.crash.title=Игра отказала!
|
||||
game.directory=Путь игры
|
||||
game.version=Сборка
|
||||
|
||||
game_directory=Папки с играми
|
||||
game_directory.already_exists=Имя уже существует, используйте другое имя.
|
||||
game_directory.default=Текущая папка
|
||||
game_directory.home=Лаунчер Minecraft
|
||||
game_directory.instance_directory=Папка с игрой
|
||||
game_directory.instance_directory.choose=Выберите папку с игрой
|
||||
game_directory.manage=Список папок для сборки
|
||||
game_directory.name=Имя
|
||||
game_directory.new=Новая папка
|
||||
game_directory.title=Папки с играми
|
||||
game_directory.selected=Выбранный
|
||||
game_directory.use_relative_path=По возможности используйте относительный путь для игры
|
||||
|
||||
help=Справка
|
||||
help.doc=Документация лаунчера
|
||||
help.detail=Для создателей набора данных и модпака.
|
||||
@@ -1123,19 +1136,6 @@ world.name=Название мира
|
||||
world.name.enter=Введите название мира
|
||||
world.show_all=Показать все
|
||||
|
||||
profile=Папки с играми
|
||||
profile.already_exists=Имя уже существует, используйте другое имя.
|
||||
profile.default=Текущая папка
|
||||
profile.home=Лаунчер Minecraft
|
||||
profile.instance_directory=Папка с игрой
|
||||
profile.instance_directory.choose=Выберите папку с игрой
|
||||
profile.manage=Список папок для сборки
|
||||
profile.name=Имя
|
||||
profile.new=Новая папка
|
||||
profile.title=Папки с играми
|
||||
profile.selected=Выбранный
|
||||
profile.use_relative_path=По возможности используйте относительный путь для игры
|
||||
|
||||
repositories.custom=Пользовательский репозиторий Maven (%s)
|
||||
repositories.maven_central=Универсальный (Maven Central)
|
||||
repositories.tencentcloud_mirror=Зеркало Mainland China (Репозиторий Tencent Cloud Maven)
|
||||
|
||||
@@ -615,6 +615,19 @@ game.crash.title=Гра зазнала збою
|
||||
game.directory=Шлях до гри
|
||||
game.version=Екземпляр гри
|
||||
|
||||
game_directory=Ігрові каталоги
|
||||
game_directory.already_exists=Це ім'я вже існує. Використовуйте інше ім'я.
|
||||
game_directory.default=Поточний
|
||||
game_directory.home=Лаунчер Minecraft
|
||||
game_directory.instance_directory=Каталог гри
|
||||
game_directory.instance_directory.choose=Вибрати каталог гри
|
||||
game_directory.manage=Список каталогів екземплярів
|
||||
game_directory.name=Назва
|
||||
game_directory.new=Новий каталог
|
||||
game_directory.title=Ігрові каталоги
|
||||
game_directory.selected=Вибраний
|
||||
game_directory.use_relative_path=Використовувати відносний шлях для каталогу гри, якщо можливо
|
||||
|
||||
help=Допомога
|
||||
help.doc=Документація Hello Minecraft! Лаунчера
|
||||
help.detail=Для творців datapack та модпаків.
|
||||
@@ -1072,19 +1085,6 @@ world.name=Назва світу
|
||||
world.name.enter=Введіть назву світу
|
||||
world.show_all=Показати все
|
||||
|
||||
profile=Ігрові каталоги
|
||||
profile.already_exists=Це ім'я вже існує. Використовуйте інше ім'я.
|
||||
profile.default=Поточний
|
||||
profile.home=Лаунчер Minecraft
|
||||
profile.instance_directory=Каталог гри
|
||||
profile.instance_directory.choose=Вибрати каталог гри
|
||||
profile.manage=Список каталогів екземплярів
|
||||
profile.name=Назва
|
||||
profile.new=Новий каталог
|
||||
profile.title=Ігрові каталоги
|
||||
profile.selected=Вибраний
|
||||
profile.use_relative_path=Використовувати відносний шлях для каталогу гри, якщо можливо
|
||||
|
||||
repositories.custom=Власне сховище Maven (%s)
|
||||
repositories.maven_central=Універсальне (Maven Central)
|
||||
repositories.tencentcloud_mirror=Дзеркало материкового Китаю (Сховище Maven Tencent Cloud)
|
||||
|
||||
@@ -549,6 +549,19 @@ game.crash.title=遊戲意外退出
|
||||
game.directory=遊戲目錄路徑
|
||||
game.version=遊戲實例
|
||||
|
||||
game_directory=遊戲目錄
|
||||
game_directory.already_exists=該名稱已存在
|
||||
game_directory.default=目前目錄
|
||||
game_directory.home=官方啟動器目錄
|
||||
game_directory.instance_directory=遊戲目錄路徑
|
||||
game_directory.instance_directory.choose=選取遊戲目錄路徑
|
||||
game_directory.manage=遊戲目錄清單
|
||||
game_directory.name=名稱
|
||||
game_directory.new=建立新目錄
|
||||
game_directory.title=遊戲目錄
|
||||
game_directory.selected=已選取
|
||||
game_directory.use_relative_path=如可行,則對於遊戲目錄使用相對路徑
|
||||
|
||||
help=說明
|
||||
help.doc=Hello Minecraft! Launcher 說明文件
|
||||
help.detail=可查閱資料包、模組包製作教學等內容
|
||||
@@ -1061,19 +1074,6 @@ world.name=世界名稱
|
||||
world.name.enter=輸入世界名稱
|
||||
world.show_all=全部顯示
|
||||
|
||||
profile=遊戲目錄
|
||||
profile.already_exists=該名稱已存在
|
||||
profile.default=目前目錄
|
||||
profile.home=官方啟動器目錄
|
||||
profile.instance_directory=遊戲目錄路徑
|
||||
profile.instance_directory.choose=選取遊戲目錄路徑
|
||||
profile.manage=遊戲目錄清單
|
||||
profile.name=名稱
|
||||
profile.new=建立新目錄
|
||||
profile.title=遊戲目錄
|
||||
profile.selected=已選取
|
||||
profile.use_relative_path=如可行,則對於遊戲目錄使用相對路徑
|
||||
|
||||
repositories.custom=自訂 Maven 儲存庫 (%s)
|
||||
repositories.maven_central=全球 (Maven Central)
|
||||
repositories.tencentcloud_mirror=中國大陸 (騰訊雲 Maven 儲存庫)
|
||||
|
||||
@@ -554,6 +554,19 @@ game.crash.title=游戏意外退出
|
||||
game.directory=游戏文件夹路径
|
||||
game.version=游戏实例
|
||||
|
||||
game_directory=游戏文件夹
|
||||
game_directory.already_exists=该名称已存在
|
||||
game_directory.default=当前文件夹
|
||||
game_directory.home=官方启动器文件夹
|
||||
game_directory.instance_directory=游戏文件夹路径
|
||||
game_directory.instance_directory.choose=选择游戏文件夹路径
|
||||
game_directory.manage=游戏文件夹列表
|
||||
game_directory.name=名称
|
||||
game_directory.new=添加游戏文件夹
|
||||
game_directory.title=游戏文件夹
|
||||
game_directory.selected=已选中
|
||||
game_directory.use_relative_path=若可能,游戏文件夹使用相对路径
|
||||
|
||||
help=帮助
|
||||
help.doc=Hello Minecraft! Launcher 帮助文档
|
||||
help.detail=可查阅数据包、整合包制作指南等内容
|
||||
@@ -1066,19 +1079,6 @@ world.name=世界名称
|
||||
world.name.enter=输入世界名称
|
||||
world.show_all=显示全部
|
||||
|
||||
profile=游戏文件夹
|
||||
profile.already_exists=该名称已存在
|
||||
profile.default=当前文件夹
|
||||
profile.home=官方启动器文件夹
|
||||
profile.instance_directory=游戏文件夹路径
|
||||
profile.instance_directory.choose=选择游戏文件夹路径
|
||||
profile.manage=游戏文件夹列表
|
||||
profile.name=名称
|
||||
profile.new=添加游戏文件夹
|
||||
profile.title=游戏文件夹
|
||||
profile.selected=已选中
|
||||
profile.use_relative_path=若可能,游戏文件夹使用相对路径
|
||||
|
||||
repositories.custom=自定义 Maven 仓库 (%s)
|
||||
repositories.maven_central=全球 (Maven Central)
|
||||
repositories.tencentcloud_mirror=中国大陆 (腾讯云镜像源 Maven 仓库)
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.google.gson.JsonParser;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.util.FileSaver;
|
||||
import org.jackhuang.hmcl.util.PortablePath;
|
||||
import org.jackhuang.hmcl.util.gson.JsonSchema;
|
||||
@@ -39,6 +40,7 @@ import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.SettingsManager.settings;
|
||||
@@ -67,7 +69,7 @@ public final class GameDirectoriesTest {
|
||||
assertFalse(settings.has("configurations"));
|
||||
assertEquals(1, gameDirectories.getGameDirectories().size());
|
||||
assertEquals(id, gameDirectories.getGameDirectories().get(0).getId());
|
||||
assertEquals("Dev", Profiles.getProfileCustomName(gameDirectories.getGameDirectories().get(0)));
|
||||
assertEquals("Dev", GameDirectoryManager.getGameDirectoryCustomName(gameDirectories.getGameDirectories().get(0)));
|
||||
assertEquals(".minecraft", gameDirectories.getGameDirectories().get(0).getPath().getPath());
|
||||
assertNull(gameDirectories.getGameDirectories().get(0).getLegacyGameSettings());
|
||||
}
|
||||
@@ -93,24 +95,24 @@ public final class GameDirectoriesTest {
|
||||
|
||||
GameDirectories gameDirectories = Objects.requireNonNull(LegacyConfigMigrator.extractGameDirectoriesFromConfigJson(settings));
|
||||
|
||||
GameDirectoryID defaultProfileId = LegacyConfigMigrator.getLegacyProfileID("Default");
|
||||
GameDirectoryID homeProfileId = LegacyConfigMigrator.getLegacyProfileID("Home");
|
||||
Profile defaultProfile = gameDirectories.getGameDirectories().stream()
|
||||
.filter(profile -> defaultProfileId.equals(profile.getId()))
|
||||
GameDirectoryID defaultGameDirectoryId = LegacyConfigMigrator.getLegacyProfileID("Default");
|
||||
GameDirectoryID homeGameDirectoryId = LegacyConfigMigrator.getLegacyProfileID("Home");
|
||||
GameDirectory defaultGameDirectory = gameDirectories.getGameDirectories().stream()
|
||||
.filter(gameDirectory -> defaultGameDirectoryId.equals(gameDirectory.getId()))
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
Profile homeProfile = gameDirectories.getGameDirectories().stream()
|
||||
.filter(profile -> homeProfileId.equals(profile.getId()))
|
||||
GameDirectory homeGameDirectory = gameDirectories.getGameDirectories().stream()
|
||||
.filter(gameDirectory -> homeGameDirectoryId.equals(gameDirectory.getId()))
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
Profile devProfile = gameDirectories.getGameDirectories().stream()
|
||||
.filter(profile -> "Dev".equals(Profiles.getProfileCustomName(profile)))
|
||||
GameDirectory devGameDirectory = gameDirectories.getGameDirectories().stream()
|
||||
.filter(gameDirectory -> "Dev".equals(GameDirectoryManager.getGameDirectoryCustomName(gameDirectory)))
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
|
||||
assertNull(defaultProfile.getName());
|
||||
assertNull(homeProfile.getName());
|
||||
assertEquals("Dev", Profiles.getProfileCustomName(devProfile));
|
||||
assertNull(defaultGameDirectory.getName());
|
||||
assertNull(homeGameDirectory.getName());
|
||||
assertEquals("Dev", GameDirectoryManager.getGameDirectoryCustomName(devGameDirectory));
|
||||
}
|
||||
|
||||
/// Tests migrating legacy selected version fields into the main config.
|
||||
@@ -168,14 +170,14 @@ public final class GameDirectoriesTest {
|
||||
assertNull(config.selectedGameDirectoryProperty().get());
|
||||
}
|
||||
|
||||
/// Tests that profiles store their directory as a portable path.
|
||||
/// Tests that game directories store their path as a portable path.
|
||||
@Test
|
||||
public void storesProfilePath() {
|
||||
public void storesGameDirectoryPath() {
|
||||
GameDirectoryID id = GameDirectoryID.parse("game-directory:123e4567-e89b-12d3-a456-426614174000");
|
||||
Profile profile = new Profile(id, LocalizedText.plain("Dev"), PortablePath.of("versions\\Dev"));
|
||||
GameDirectory gameDirectory = new GameDirectory(id, LocalizedText.plain("Dev"), PortablePath.of("versions\\Dev"));
|
||||
|
||||
JsonObject serialized = JsonUtils.GSON.toJsonTree(profile, Profile.class).getAsJsonObject();
|
||||
Profile deserialized = Objects.requireNonNull(JsonUtils.GSON.fromJson(serialized, Profile.class));
|
||||
JsonObject serialized = JsonUtils.GSON.toJsonTree(gameDirectory, GameDirectory.class).getAsJsonObject();
|
||||
GameDirectory deserialized = Objects.requireNonNull(JsonUtils.GSON.fromJson(serialized, GameDirectory.class));
|
||||
|
||||
assertEquals("versions/Dev", serialized.get("path").getAsString());
|
||||
assertEquals("Dev", serialized.get("name").getAsString());
|
||||
@@ -185,202 +187,228 @@ public final class GameDirectoriesTest {
|
||||
assertFalse(deserialized.getPath().isAbsolute());
|
||||
}
|
||||
|
||||
/// Tests that profiles preserve migrated legacy game settings IDs.
|
||||
/// Tests that game directories preserve migrated legacy game settings IDs.
|
||||
@Test
|
||||
public void storesLegacyGameSettingsId() {
|
||||
GameDirectoryID id = GameDirectoryID.parse("game-directory:123e4567-e89b-12d3-a456-426614174000");
|
||||
GameSettingsPresetID legacyGameSettings =
|
||||
GameSettingsPresetID.parse("game-settings-preset:123e4567-e89b-12d3-a456-426614174001");
|
||||
Profile profile = new Profile(
|
||||
GameDirectory gameDirectory = new GameDirectory(
|
||||
id,
|
||||
LocalizedText.plain("Dev"),
|
||||
PortablePath.of("versions\\Dev"),
|
||||
legacyGameSettings);
|
||||
|
||||
JsonObject serialized = JsonUtils.GSON.toJsonTree(profile, Profile.class).getAsJsonObject();
|
||||
Profile deserialized = Objects.requireNonNull(JsonUtils.GSON.fromJson(serialized, Profile.class));
|
||||
JsonObject serialized = JsonUtils.GSON.toJsonTree(gameDirectory, GameDirectory.class).getAsJsonObject();
|
||||
GameDirectory deserialized = Objects.requireNonNull(JsonUtils.GSON.fromJson(serialized, GameDirectory.class));
|
||||
|
||||
assertEquals(legacyGameSettings.toString(), serialized.get("legacyGameSettings").getAsString());
|
||||
assertEquals(legacyGameSettings, deserialized.getLegacyGameSettings());
|
||||
}
|
||||
|
||||
/// Tests that the merged profile view is read-only and does not own the backing profile data.
|
||||
/// Tests that the merged game directory view is read-only and does not own the backing store data.
|
||||
@Test
|
||||
public void keepsShadowedUserProfileInBackingStore() throws ReflectiveOperationException {
|
||||
public void keepsShadowedUserGameDirectoryInBackingStore() throws ReflectiveOperationException {
|
||||
GameDirectoryID id = GameDirectoryID.parse("game-directory:123e4567-e89b-12d3-a456-426614174000");
|
||||
Profile userProfile = new Profile(id, LocalizedText.plain("User"), PortablePath.of("user/Dev"));
|
||||
Profile localProfile = new Profile(id, LocalizedText.plain("Local"), PortablePath.of("local/Dev"));
|
||||
Profile addedProfile = new Profile(
|
||||
GameDirectory userGameDirectory = new GameDirectory(id, LocalizedText.plain("User"), PortablePath.of("user/Dev"));
|
||||
GameDirectory localGameDirectory = new GameDirectory(id, LocalizedText.plain("Local"), PortablePath.of("local/Dev"));
|
||||
GameDirectory addedGameDirectory = new GameDirectory(
|
||||
GameDirectoryID.parse("game-directory:123e4567-e89b-12d3-a456-426614174001"),
|
||||
LocalizedText.plain("Added"),
|
||||
PortablePath.of("local/Added"));
|
||||
GameDirectories userDirectories = new GameDirectories();
|
||||
userDirectories.getGameDirectories().add(userProfile);
|
||||
userDirectories.getGameDirectories().add(userGameDirectory);
|
||||
userDirectories.setUserFile(true);
|
||||
GameDirectories localDirectories = new GameDirectories();
|
||||
localDirectories.getGameDirectories().add(localProfile);
|
||||
localDirectories.getGameDirectories().add(localGameDirectory);
|
||||
localDirectories.setUserFile(false);
|
||||
|
||||
try (ProfileEnvironment ignored = new ProfileEnvironment(localDirectories, userDirectories)) {
|
||||
Profiles.init();
|
||||
ObservableList<Profile> gameDirectories = Profiles.getProfiles();
|
||||
try (GameDirectoryEnvironment ignored = new GameDirectoryEnvironment(localDirectories, userDirectories)) {
|
||||
GameDirectoryManager.init();
|
||||
ObservableList<GameDirectory> gameDirectories = GameDirectoryManager.getGameDirectories();
|
||||
|
||||
assertEquals(List.of(localProfile), gameDirectories);
|
||||
assertThrows(UnsupportedOperationException.class, () -> gameDirectories.add(addedProfile));
|
||||
assertSame(localProfile, Profiles.getSelectedProfile());
|
||||
assertEquals(localProfile.getId(), settings().selectedGameDirectoryProperty().get());
|
||||
assertEquals(List.of(userProfile), userDirectories.getGameDirectories());
|
||||
assertEquals(List.of(localProfile), localDirectories.getGameDirectories());
|
||||
assertEquals(List.of(localGameDirectory), gameDirectories);
|
||||
assertThrows(UnsupportedOperationException.class, () -> gameDirectories.add(addedGameDirectory));
|
||||
assertSame(localGameDirectory, GameDirectoryManager.getSelectedGameDirectory());
|
||||
assertEquals(localGameDirectory.getId(), settings().selectedGameDirectoryProperty().get());
|
||||
assertEquals(List.of(userGameDirectory), userDirectories.getGameDirectories());
|
||||
assertEquals(List.of(localGameDirectory), localDirectories.getGameDirectories());
|
||||
|
||||
Profiles.removeProfile(localProfile);
|
||||
assertEquals(List.of(userProfile), Profiles.getProfiles());
|
||||
assertSame(userProfile, Profiles.getSelectedProfile());
|
||||
assertEquals(userProfile.getId(), settings().selectedGameDirectoryProperty().get());
|
||||
assertEquals(List.of(userProfile), userDirectories.getGameDirectories());
|
||||
GameDirectoryManager.removeGameDirectory(localGameDirectory);
|
||||
assertEquals(List.of(userGameDirectory), GameDirectoryManager.getGameDirectories());
|
||||
assertSame(userGameDirectory, GameDirectoryManager.getSelectedGameDirectory());
|
||||
assertEquals(userGameDirectory.getId(), settings().selectedGameDirectoryProperty().get());
|
||||
assertEquals(List.of(userGameDirectory), userDirectories.getGameDirectories());
|
||||
assertTrue(localDirectories.getGameDirectories().isEmpty());
|
||||
|
||||
Profiles.addLocalProfile(addedProfile);
|
||||
assertEquals(List.of(addedProfile, userProfile), Profiles.getProfiles());
|
||||
assertEquals(List.of(addedProfile), localDirectories.getGameDirectories());
|
||||
assertSame(userProfile, Profiles.getSelectedProfile());
|
||||
GameDirectoryManager.addLocalGameDirectory(addedGameDirectory);
|
||||
assertEquals(List.of(addedGameDirectory, userGameDirectory), GameDirectoryManager.getGameDirectories());
|
||||
assertEquals(List.of(addedGameDirectory), localDirectories.getGameDirectories());
|
||||
assertSame(userGameDirectory, GameDirectoryManager.getSelectedGameDirectory());
|
||||
|
||||
Profiles.setSelectedProfile(addedProfile);
|
||||
assertSame(addedProfile, Profiles.getSelectedProfile());
|
||||
assertEquals(addedProfile.getId(), settings().selectedGameDirectoryProperty().get());
|
||||
GameDirectoryManager.setSelectedGameDirectory(addedGameDirectory);
|
||||
assertSame(addedGameDirectory, GameDirectoryManager.getSelectedGameDirectory());
|
||||
assertEquals(addedGameDirectory.getId(), settings().selectedGameDirectoryProperty().get());
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests that editing a profile moves it to the store selected by its new path type.
|
||||
/// Tests that editing a game directory moves it to the store selected by its new path type.
|
||||
@Test
|
||||
public void movesProfileBetweenStoresWhenPathTypeChanges() throws ReflectiveOperationException {
|
||||
public void movesGameDirectoryBetweenStoresWhenPathTypeChanges() throws ReflectiveOperationException {
|
||||
GameDirectoryID id = GameDirectoryID.parse("game-directory:123e4567-e89b-12d3-a456-426614174000");
|
||||
Profile profile = new Profile(id, LocalizedText.plain("Local"), PortablePath.of("local/Dev"));
|
||||
GameDirectory gameDirectory = new GameDirectory(id, LocalizedText.plain("Local"), PortablePath.of("local/Dev"));
|
||||
GameDirectories userDirectories = new GameDirectories();
|
||||
userDirectories.setUserFile(true);
|
||||
GameDirectories localDirectories = new GameDirectories();
|
||||
localDirectories.getGameDirectories().add(profile);
|
||||
localDirectories.getGameDirectories().add(gameDirectory);
|
||||
localDirectories.setUserFile(false);
|
||||
|
||||
try (ProfileEnvironment ignored = new ProfileEnvironment(localDirectories, userDirectories)) {
|
||||
Profiles.init();
|
||||
try (GameDirectoryEnvironment ignored = new GameDirectoryEnvironment(localDirectories, userDirectories)) {
|
||||
GameDirectoryManager.init();
|
||||
|
||||
PortablePath absolutePath = PortablePath.of("/workspace/Dev");
|
||||
Profiles.updateProfile(profile, LocalizedText.plain("Moved"), absolutePath);
|
||||
GameDirectoryManager.updateGameDirectory(gameDirectory, LocalizedText.plain("Moved"), absolutePath);
|
||||
|
||||
assertTrue(localDirectories.getGameDirectories().isEmpty());
|
||||
assertEquals(List.of(profile), userDirectories.getGameDirectories());
|
||||
assertEquals(List.of(profile), Profiles.getProfiles());
|
||||
assertSame(profile, Profiles.getSelectedProfile());
|
||||
assertEquals(absolutePath.getPath(), profile.getPath().getPath());
|
||||
assertEquals(absolutePath.isAbsolute(), profile.getPath().isAbsolute());
|
||||
assertEquals("Moved", Profiles.getProfileCustomName(profile));
|
||||
assertEquals(List.of(gameDirectory), userDirectories.getGameDirectories());
|
||||
assertEquals(List.of(gameDirectory), GameDirectoryManager.getGameDirectories());
|
||||
assertSame(gameDirectory, GameDirectoryManager.getSelectedGameDirectory());
|
||||
assertEquals(absolutePath.getPath(), gameDirectory.getPath().getPath());
|
||||
assertEquals(absolutePath.isAbsolute(), gameDirectory.getPath().isAbsolute());
|
||||
assertEquals("Moved", GameDirectoryManager.getGameDirectoryCustomName(gameDirectory));
|
||||
|
||||
PortablePath relativePath = PortablePath.of("local/Dev");
|
||||
Profiles.updateProfile(profile, LocalizedText.plain("Back"), relativePath);
|
||||
GameDirectoryManager.updateGameDirectory(gameDirectory, LocalizedText.plain("Back"), relativePath);
|
||||
|
||||
assertEquals(List.of(profile), localDirectories.getGameDirectories());
|
||||
assertEquals(List.of(gameDirectory), localDirectories.getGameDirectories());
|
||||
assertTrue(userDirectories.getGameDirectories().isEmpty());
|
||||
assertEquals(List.of(profile), Profiles.getProfiles());
|
||||
assertSame(profile, Profiles.getSelectedProfile());
|
||||
assertEquals(relativePath.getPath(), profile.getPath().getPath());
|
||||
assertEquals(relativePath.isAbsolute(), profile.getPath().isAbsolute());
|
||||
assertEquals("Back", Profiles.getProfileCustomName(profile));
|
||||
assertEquals(List.of(gameDirectory), GameDirectoryManager.getGameDirectories());
|
||||
assertSame(gameDirectory, GameDirectoryManager.getSelectedGameDirectory());
|
||||
assertEquals(relativePath.getPath(), gameDirectory.getPath().getPath());
|
||||
assertEquals(relativePath.isAbsolute(), gameDirectory.getPath().isAbsolute());
|
||||
assertEquals("Back", GameDirectoryManager.getGameDirectoryCustomName(gameDirectory));
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests that profile mutations reject writes to read-only source and target stores.
|
||||
/// Tests that game directory mutations reject writes to read-only source and target stores.
|
||||
@Test
|
||||
public void rejectsProfileMutationsWithReadOnlyStores() throws ReflectiveOperationException {
|
||||
public void rejectsGameDirectoryMutationsWithReadOnlyStores() throws ReflectiveOperationException {
|
||||
GameDirectoryID id = GameDirectoryID.parse("game-directory:123e4567-e89b-12d3-a456-426614174000");
|
||||
Profile profile = new Profile(id, LocalizedText.plain("Local"), PortablePath.of("local/Dev"));
|
||||
GameDirectory gameDirectory = new GameDirectory(id, LocalizedText.plain("Local"), PortablePath.of("local/Dev"));
|
||||
GameDirectories userDirectories = new GameDirectories();
|
||||
userDirectories.setUserFile(true);
|
||||
GameDirectories localDirectories = new GameDirectories();
|
||||
localDirectories.getGameDirectories().add(profile);
|
||||
localDirectories.getGameDirectories().add(gameDirectory);
|
||||
localDirectories.setUserFile(false);
|
||||
|
||||
try (ProfileEnvironment environment = new ProfileEnvironment(localDirectories, userDirectories)) {
|
||||
try (GameDirectoryEnvironment environment = new GameDirectoryEnvironment(localDirectories, userDirectories)) {
|
||||
environment.setLocalGameDirectoriesAccess(SettingFileAccess.READ_ONLY);
|
||||
Profiles.init();
|
||||
GameDirectoryManager.init();
|
||||
|
||||
PortablePath newPath = PortablePath.of("local/Renamed");
|
||||
assertFalse(Profiles.canUpdateProfile(profile, newPath));
|
||||
assertFalse(GameDirectoryManager.canUpdateGameDirectory(gameDirectory, newPath));
|
||||
assertThrows(IllegalStateException.class,
|
||||
() -> Profiles.updateProfile(profile, LocalizedText.plain("Renamed"), newPath));
|
||||
assertFalse(Profiles.canRemoveProfile(profile));
|
||||
assertThrows(IllegalStateException.class, () -> Profiles.removeProfile(profile));
|
||||
assertEquals(List.of(profile), localDirectories.getGameDirectories());
|
||||
() -> GameDirectoryManager.updateGameDirectory(gameDirectory, LocalizedText.plain("Renamed"), newPath));
|
||||
assertFalse(GameDirectoryManager.canRemoveGameDirectory(gameDirectory));
|
||||
assertThrows(IllegalStateException.class, () -> GameDirectoryManager.removeGameDirectory(gameDirectory));
|
||||
assertEquals(List.of(gameDirectory), localDirectories.getGameDirectories());
|
||||
assertTrue(userDirectories.getGameDirectories().isEmpty());
|
||||
assertEquals("local/Dev", profile.getPath().getPath());
|
||||
assertFalse(profile.getPath().isAbsolute());
|
||||
assertEquals("Local", Profiles.getProfileCustomName(profile));
|
||||
assertEquals("local/Dev", gameDirectory.getPath().getPath());
|
||||
assertFalse(gameDirectory.getPath().isAbsolute());
|
||||
assertEquals("Local", GameDirectoryManager.getGameDirectoryCustomName(gameDirectory));
|
||||
}
|
||||
|
||||
Profile targetProfile = new Profile(id, LocalizedText.plain("Local"), PortablePath.of("local/Dev"));
|
||||
GameDirectory targetGameDirectory = new GameDirectory(id, LocalizedText.plain("Local"), PortablePath.of("local/Dev"));
|
||||
GameDirectories targetUserDirectories = new GameDirectories();
|
||||
targetUserDirectories.setUserFile(true);
|
||||
GameDirectories targetLocalDirectories = new GameDirectories();
|
||||
targetLocalDirectories.getGameDirectories().add(targetProfile);
|
||||
targetLocalDirectories.getGameDirectories().add(targetGameDirectory);
|
||||
targetLocalDirectories.setUserFile(false);
|
||||
|
||||
try (ProfileEnvironment environment = new ProfileEnvironment(targetLocalDirectories, targetUserDirectories)) {
|
||||
try (GameDirectoryEnvironment environment = new GameDirectoryEnvironment(targetLocalDirectories, targetUserDirectories)) {
|
||||
environment.setUserGameDirectoriesAccess(SettingFileAccess.READ_ONLY);
|
||||
Profiles.init();
|
||||
GameDirectoryManager.init();
|
||||
|
||||
PortablePath absolutePath = PortablePath.of("/workspace/Dev");
|
||||
assertFalse(Profiles.canUpdateProfile(targetProfile, absolutePath));
|
||||
assertFalse(GameDirectoryManager.canUpdateGameDirectory(targetGameDirectory, absolutePath));
|
||||
assertThrows(IllegalStateException.class,
|
||||
() -> Profiles.updateProfile(targetProfile, LocalizedText.plain("Moved"), absolutePath));
|
||||
assertEquals(List.of(targetProfile), targetLocalDirectories.getGameDirectories());
|
||||
() -> GameDirectoryManager.updateGameDirectory(targetGameDirectory, LocalizedText.plain("Moved"), absolutePath));
|
||||
assertEquals(List.of(targetGameDirectory), targetLocalDirectories.getGameDirectories());
|
||||
assertTrue(targetUserDirectories.getGameDirectories().isEmpty());
|
||||
assertEquals("local/Dev", targetProfile.getPath().getPath());
|
||||
assertFalse(targetProfile.getPath().isAbsolute());
|
||||
assertEquals("Local", Profiles.getProfileCustomName(targetProfile));
|
||||
assertEquals("local/Dev", targetGameDirectory.getPath().getPath());
|
||||
assertFalse(targetGameDirectory.getPath().isAbsolute());
|
||||
assertEquals("Local", GameDirectoryManager.getGameDirectoryCustomName(targetGameDirectory));
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests that default profiles are created when both loaded stores are empty.
|
||||
/// Tests that default game directories are created when both loaded stores are empty.
|
||||
@Test
|
||||
public void createsDefaultProfilesWhenMergedProfilesAreEmpty() throws ReflectiveOperationException {
|
||||
public void createsDefaultGameDirectoriesWhenMergedGameDirectoriesAreEmpty() throws ReflectiveOperationException {
|
||||
GameDirectories userDirectories = new GameDirectories();
|
||||
userDirectories.setUserFile(true);
|
||||
GameDirectories localDirectories = new GameDirectories();
|
||||
localDirectories.setUserFile(false);
|
||||
|
||||
try (ProfileEnvironment ignored = new ProfileEnvironment(localDirectories, userDirectories)) {
|
||||
Profiles.init();
|
||||
try (GameDirectoryEnvironment ignored = new GameDirectoryEnvironment(localDirectories, userDirectories)) {
|
||||
GameDirectoryManager.init();
|
||||
|
||||
Profile localProfile = assertSingleDefaultProfile(localDirectories, PortablePath.of(".minecraft"));
|
||||
Profile userProfile = assertSingleDefaultProfile(
|
||||
GameDirectory localGameDirectory = assertSingleDefaultGameDirectory(localDirectories, PortablePath.of(".minecraft"));
|
||||
GameDirectory userGameDirectory = assertSingleDefaultGameDirectory(
|
||||
userDirectories,
|
||||
PortablePath.fromPath(Metadata.MINECRAFT_DIRECTORY));
|
||||
assertEquals(2, Profiles.getProfiles().size());
|
||||
assertTrue(Profiles.getProfiles().contains(localProfile));
|
||||
assertTrue(Profiles.getProfiles().contains(userProfile));
|
||||
assertEquals(2, GameDirectoryManager.getGameDirectories().size());
|
||||
assertTrue(GameDirectoryManager.getGameDirectories().contains(localGameDirectory));
|
||||
assertTrue(GameDirectoryManager.getGameDirectories().contains(userGameDirectory));
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests that the selected profile getter restores a valid selection instead of returning null.
|
||||
/// Tests that the selected game directory getter restores a valid selection instead of returning null.
|
||||
@Test
|
||||
public void selectedProfileGetterNeverReturnsNullWhenProfilesAreLoaded() throws ReflectiveOperationException {
|
||||
public void selectedGameDirectoryGetterNeverReturnsNullWhenGameDirectoriesAreLoaded() throws ReflectiveOperationException {
|
||||
GameDirectories userDirectories = new GameDirectories();
|
||||
userDirectories.setUserFile(true);
|
||||
GameDirectories localDirectories = new GameDirectories();
|
||||
localDirectories.setUserFile(false);
|
||||
|
||||
try (ProfileEnvironment ignored = new ProfileEnvironment(localDirectories, userDirectories)) {
|
||||
Profiles.init();
|
||||
try (GameDirectoryEnvironment ignored = new GameDirectoryEnvironment(localDirectories, userDirectories)) {
|
||||
GameDirectoryManager.init();
|
||||
|
||||
Profile selected = Profiles.getSelectedProfile();
|
||||
GameDirectory selected = GameDirectoryManager.getSelectedGameDirectory();
|
||||
assertNotNull(selected);
|
||||
assertSame(selected, Profiles.getSelectedProfile());
|
||||
assertTrue(Profiles.getProfiles().contains(selected));
|
||||
assertSame(selected, GameDirectoryManager.getSelectedGameDirectory());
|
||||
assertTrue(GameDirectoryManager.getGameDirectories().contains(selected));
|
||||
assertEquals(selected.getId(), settings().selectedGameDirectoryProperty().get());
|
||||
}
|
||||
}
|
||||
|
||||
/// Temporary static state override for profile tests.
|
||||
private static final class ProfileEnvironment implements AutoCloseable {
|
||||
/// Tests that a repository follows changes to its game directory path.
|
||||
@Test
|
||||
public void repositoryDirectoryFollowsGameDirectoryPath() throws ReflectiveOperationException {
|
||||
GameDirectories userDirectories = new GameDirectories();
|
||||
userDirectories.setUserFile(true);
|
||||
GameDirectories localDirectories = new GameDirectories();
|
||||
localDirectories.setUserFile(false);
|
||||
GameDirectory gameDirectory = new GameDirectory(
|
||||
GameDirectoryID.generate(),
|
||||
LocalizedText.plain("Local"),
|
||||
PortablePath.of("local/Dev"));
|
||||
localDirectories.getGameDirectories().add(gameDirectory);
|
||||
|
||||
try (GameDirectoryEnvironment ignored = new GameDirectoryEnvironment(localDirectories, userDirectories)) {
|
||||
GameDirectoryManager.init();
|
||||
|
||||
HMCLGameRepository repository = GameDirectoryManager.getSelectedRepository();
|
||||
assertSame(gameDirectory, repository.getGameDirectory());
|
||||
assertEquals(gameDirectory.getPath().toPath(), repository.getBaseDirectory());
|
||||
|
||||
PortablePath newPath = PortablePath.of("local/Renamed");
|
||||
gameDirectory.setPath(newPath);
|
||||
assertEquals(newPath.toPath(), repository.getBaseDirectory());
|
||||
}
|
||||
}
|
||||
|
||||
/// Temporary static state override for game directory tests.
|
||||
private static final class GameDirectoryEnvironment implements AutoCloseable {
|
||||
/// The reflected SettingsManager local game directories field.
|
||||
private final Field localGameDirectoriesField;
|
||||
|
||||
@@ -396,14 +424,20 @@ public final class GameDirectoriesTest {
|
||||
/// The reflected SettingsManager user game directories access field.
|
||||
private final Field userGameDirectoriesAccessField;
|
||||
|
||||
/// The reflected Profiles initialized field.
|
||||
/// The reflected GameDirectoryManager initialized field.
|
||||
private final Field initializedField;
|
||||
|
||||
/// The reflected Profiles selected profile property.
|
||||
private final ObjectProperty<Profile> selectedProfile;
|
||||
/// The reflected GameDirectoryManager selected game directory property.
|
||||
private final ObjectProperty<GameDirectory> selectedGameDirectory;
|
||||
|
||||
/// The merged profile list used by Profiles.
|
||||
private final ObservableList<Profile> mergedProfiles;
|
||||
/// The reflected GameDirectoryManager selected repository property.
|
||||
private final ObjectProperty<HMCLGameRepository> selectedRepository;
|
||||
|
||||
/// The merged game directory list used by GameDirectoryManager.
|
||||
private final ObservableList<GameDirectory> mergedGameDirectories;
|
||||
|
||||
/// The repositories mapped by GameDirectoryManager.
|
||||
private final Map<GameDirectory, HMCLGameRepository> repositories;
|
||||
|
||||
/// The previous local game directories instance.
|
||||
private final Object previousLocalGameDirectories;
|
||||
@@ -420,34 +454,44 @@ public final class GameDirectoriesTest {
|
||||
/// The previous user game directories access.
|
||||
private final SettingFileAccess previousUserGameDirectoriesAccess;
|
||||
|
||||
/// The previous Profiles initialization state.
|
||||
/// The previous GameDirectoryManager initialization state.
|
||||
private final boolean previousInitialized;
|
||||
|
||||
/// The previous selected profile.
|
||||
private final Profile previousSelectedProfile;
|
||||
/// The previous selected game directory.
|
||||
private final GameDirectory previousSelectedGameDirectory;
|
||||
|
||||
/// The previous merged profiles.
|
||||
private final List<Profile> previousMergedProfiles;
|
||||
/// The previous selected repository.
|
||||
private final HMCLGameRepository previousSelectedRepository;
|
||||
|
||||
/// Replaces profile-related static state with the given stores.
|
||||
private ProfileEnvironment(GameDirectories localDirectories, GameDirectories userDirectories)
|
||||
/// The previous merged game directories.
|
||||
private final List<GameDirectory> previousMergedGameDirectories;
|
||||
|
||||
/// The previous repository map entries.
|
||||
private final Map<GameDirectory, HMCLGameRepository> previousRepositories;
|
||||
|
||||
/// Replaces game-directory-related static state with the given stores.
|
||||
private GameDirectoryEnvironment(GameDirectories localDirectories, GameDirectories userDirectories)
|
||||
throws ReflectiveOperationException {
|
||||
localGameDirectoriesField = SettingsManager.class.getDeclaredField("localGameDirectories");
|
||||
userGameDirectoriesField = SettingsManager.class.getDeclaredField("userGameDirectories");
|
||||
launcherSettingsField = SettingsManager.class.getDeclaredField("launcherSettings");
|
||||
localGameDirectoriesAccessField = SettingsManager.class.getDeclaredField("localGameDirectoriesAccess");
|
||||
userGameDirectoriesAccessField = SettingsManager.class.getDeclaredField("userGameDirectoriesAccess");
|
||||
initializedField = Profiles.class.getDeclaredField("initialized");
|
||||
Field selectedProfileField = Profiles.class.getDeclaredField("selectedProfile");
|
||||
Field mergedProfilesField = Profiles.class.getDeclaredField("mergedProfiles");
|
||||
initializedField = GameDirectoryManager.class.getDeclaredField("initialized");
|
||||
Field selectedGameDirectoryField = GameDirectoryManager.class.getDeclaredField("selectedGameDirectory");
|
||||
Field selectedRepositoryField = GameDirectoryManager.class.getDeclaredField("selectedRepository");
|
||||
Field mergedGameDirectoriesField = GameDirectoryManager.class.getDeclaredField("mergedGameDirectories");
|
||||
Field repositoriesField = GameDirectoryManager.class.getDeclaredField("repositories");
|
||||
localGameDirectoriesField.setAccessible(true);
|
||||
userGameDirectoriesField.setAccessible(true);
|
||||
launcherSettingsField.setAccessible(true);
|
||||
localGameDirectoriesAccessField.setAccessible(true);
|
||||
userGameDirectoriesAccessField.setAccessible(true);
|
||||
initializedField.setAccessible(true);
|
||||
selectedProfileField.setAccessible(true);
|
||||
mergedProfilesField.setAccessible(true);
|
||||
selectedGameDirectoryField.setAccessible(true);
|
||||
selectedRepositoryField.setAccessible(true);
|
||||
mergedGameDirectoriesField.setAccessible(true);
|
||||
repositoriesField.setAccessible(true);
|
||||
|
||||
previousLocalGameDirectories = localGameDirectoriesField.get(null);
|
||||
previousUserGameDirectories = userGameDirectoriesField.get(null);
|
||||
@@ -457,16 +501,28 @@ public final class GameDirectoriesTest {
|
||||
previousInitialized = initializedField.getBoolean(null);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ObjectProperty<Profile> selectedProfile =
|
||||
(ObjectProperty<Profile>) selectedProfileField.get(null);
|
||||
this.selectedProfile = selectedProfile;
|
||||
previousSelectedProfile = selectedProfile.get();
|
||||
ObjectProperty<GameDirectory> selectedGameDirectory =
|
||||
(ObjectProperty<GameDirectory>) selectedGameDirectoryField.get(null);
|
||||
this.selectedGameDirectory = selectedGameDirectory;
|
||||
previousSelectedGameDirectory = selectedGameDirectory.get();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ObservableList<Profile> mergedProfiles =
|
||||
(ObservableList<Profile>) mergedProfilesField.get(null);
|
||||
this.mergedProfiles = mergedProfiles;
|
||||
previousMergedProfiles = List.copyOf(mergedProfiles);
|
||||
ObjectProperty<HMCLGameRepository> selectedRepository =
|
||||
(ObjectProperty<HMCLGameRepository>) selectedRepositoryField.get(null);
|
||||
this.selectedRepository = selectedRepository;
|
||||
previousSelectedRepository = selectedRepository.get();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ObservableList<GameDirectory> mergedGameDirectories =
|
||||
(ObservableList<GameDirectory>) mergedGameDirectoriesField.get(null);
|
||||
this.mergedGameDirectories = mergedGameDirectories;
|
||||
previousMergedGameDirectories = List.copyOf(mergedGameDirectories);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<GameDirectory, HMCLGameRepository> repositories =
|
||||
(Map<GameDirectory, HMCLGameRepository>) repositoriesField.get(null);
|
||||
this.repositories = repositories;
|
||||
previousRepositories = Map.copyOf(repositories);
|
||||
|
||||
localGameDirectoriesField.set(null, localDirectories);
|
||||
userGameDirectoriesField.set(null, userDirectories);
|
||||
@@ -474,7 +530,9 @@ public final class GameDirectoriesTest {
|
||||
localGameDirectoriesAccessField.set(null, SettingFileAccess.READ_WRITE);
|
||||
userGameDirectoriesAccessField.set(null, SettingFileAccess.READ_WRITE);
|
||||
initializedField.setBoolean(null, false);
|
||||
mergedProfiles.clear();
|
||||
mergedGameDirectories.clear();
|
||||
repositories.clear();
|
||||
selectedRepository.set(null);
|
||||
}
|
||||
|
||||
/// Sets the local game directories access used by [SettingsManager].
|
||||
@@ -490,10 +548,13 @@ public final class GameDirectoriesTest {
|
||||
/// Restores the previous static state.
|
||||
@Override
|
||||
public void close() throws ReflectiveOperationException {
|
||||
if (previousSelectedProfile != null) {
|
||||
selectedProfile.set(previousSelectedProfile);
|
||||
if (previousSelectedGameDirectory != null) {
|
||||
selectedGameDirectory.set(previousSelectedGameDirectory);
|
||||
}
|
||||
mergedProfiles.setAll(previousMergedProfiles);
|
||||
selectedRepository.set(previousSelectedRepository);
|
||||
mergedGameDirectories.setAll(previousMergedGameDirectories);
|
||||
repositories.clear();
|
||||
repositories.putAll(previousRepositories);
|
||||
localGameDirectoriesField.set(null, previousLocalGameDirectories);
|
||||
userGameDirectoriesField.set(null, previousUserGameDirectories);
|
||||
launcherSettingsField.set(null, previousLauncherSettings);
|
||||
@@ -503,25 +564,25 @@ public final class GameDirectoriesTest {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the only default profile in the given store, asserting its path.
|
||||
private static Profile assertSingleDefaultProfile(GameDirectories gameDirectories, PortablePath path) {
|
||||
/// Returns the only default game directory in the given store, asserting its path.
|
||||
private static GameDirectory assertSingleDefaultGameDirectory(GameDirectories gameDirectories, PortablePath path) {
|
||||
assertEquals(1, gameDirectories.getGameDirectories().size());
|
||||
Profile profile = gameDirectories.getGameDirectories().get(0);
|
||||
assertEquals(path.isAbsolute(), profile.getPath().isAbsolute());
|
||||
assertEquals(path.getPath(), profile.getPath().getPath());
|
||||
return profile;
|
||||
GameDirectory gameDirectory = gameDirectories.getGameDirectories().get(0);
|
||||
assertEquals(path.isAbsolute(), gameDirectory.getPath().isAbsolute());
|
||||
assertEquals(path.getPath(), gameDirectory.getPath().getPath());
|
||||
return gameDirectory;
|
||||
}
|
||||
|
||||
/// Tests that profiles must be deserialized with a non-nil ID.
|
||||
/// Tests that game directories must be deserialized with a non-nil ID.
|
||||
@Test
|
||||
public void rejectsNilProfileId() {
|
||||
public void rejectsNilGameDirectoryId() {
|
||||
assertThrows(JsonParseException.class, () -> JsonUtils.GSON.fromJson("""
|
||||
{
|
||||
"id": "game-directory:00000000-0000-0000-0000-000000000000",
|
||||
"name": "Dev",
|
||||
"path": "versions/Dev"
|
||||
}
|
||||
""", Profile.class));
|
||||
""", GameDirectory.class));
|
||||
}
|
||||
|
||||
/// Tests that newer minor-version schemas are reported as unsupported.
|
||||
|
||||
@@ -132,10 +132,10 @@ public final class GameSettingsPresetsTest {
|
||||
LegacyConfigMigrator.migrateLegacyPresetSettings(gameDirectories, presets, configurations);
|
||||
|
||||
assertEquals(1, presets.getPresets().size());
|
||||
Profile profile = gameDirectories.getGameDirectories().get(0);
|
||||
GameDirectory gameDirectory = gameDirectories.getGameDirectories().get(0);
|
||||
GameSettings.Preset preset = presets.getPresets().get(0);
|
||||
assertEquals(profile.getLegacyGameSettings(), preset.idProperty().getValue());
|
||||
assertNotEquals(profile.getId(), preset.idProperty().getValue());
|
||||
assertEquals(gameDirectory.getLegacyGameSettings(), preset.idProperty().getValue());
|
||||
assertNotEquals(gameDirectory.getId(), preset.idProperty().getValue());
|
||||
assertEquals(2048, preset.maxMemoryProperty().getValue());
|
||||
}
|
||||
}
|
||||
@@ -18,12 +18,12 @@
|
||||
"directories": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/profile"
|
||||
"$ref": "#/$defs/gameDirectory"
|
||||
}
|
||||
}
|
||||
},
|
||||
"$defs": {
|
||||
"profile": {
|
||||
"gameDirectory": {
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"required": [
|
||||
|
||||
Reference in new issue
Block a user