Added better client command-line config, and set up simple application packaging pipeline.
This commit is contained in:
parent
16b7a1e653
commit
f7959796b4
|
@ -26,7 +26,11 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
|
||||||
public class Client implements Runnable {
|
public class Client implements Runnable {
|
||||||
|
private final String host;
|
||||||
|
private final int port;
|
||||||
|
private final String username;
|
||||||
private final ClientConfig config;
|
private final ClientConfig config;
|
||||||
|
|
||||||
private final CommunicationHandler communicationHandler;
|
private final CommunicationHandler communicationHandler;
|
||||||
private final InputHandler inputHandler;
|
private final InputHandler inputHandler;
|
||||||
private final Camera camera;
|
private final Camera camera;
|
||||||
|
@ -42,7 +46,10 @@ public class Client implements Runnable {
|
||||||
private final Chat chat;
|
private final Chat chat;
|
||||||
private final Queue<Runnable> mainThreadActions;
|
private final Queue<Runnable> mainThreadActions;
|
||||||
|
|
||||||
public Client(ClientConfig config) {
|
public Client(ClientConfig config, String host, int port, String username) {
|
||||||
|
this.host = host;
|
||||||
|
this.port = port;
|
||||||
|
this.username = username;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.camera = new Camera();
|
this.camera = new Camera();
|
||||||
this.players = new ConcurrentHashMap<>();
|
this.players = new ConcurrentHashMap<>();
|
||||||
|
@ -74,7 +81,7 @@ public class Client implements Runnable {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
communicationHandler.establishConnection();
|
communicationHandler.establishConnection(host, port, username);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
System.err.println("Couldn't connect to the server: " + e.getMessage());
|
System.err.println("Couldn't connect to the server: " + e.getMessage());
|
||||||
return;
|
return;
|
||||||
|
@ -265,13 +272,21 @@ public class Client implements Runnable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
public static void main(String[] args) throws IOException {
|
||||||
|
if (args.length < 3) {
|
||||||
|
System.err.println("Missing required host, port, username args.");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
String host = args[0].trim();
|
||||||
|
int port = Integer.parseInt(args[1]);
|
||||||
|
String username = args[2].trim();
|
||||||
|
|
||||||
List<Path> configPaths = Config.getCommonConfigPaths();
|
List<Path> configPaths = Config.getCommonConfigPaths();
|
||||||
configPaths.add(0, Path.of("client.yaml")); // Add this first so we create client.yaml if needed.
|
configPaths.add(0, Path.of("client.yaml")); // Add this first so we create client.yaml if needed.
|
||||||
if (args.length > 0) {
|
if (args.length > 3) {
|
||||||
configPaths.add(Path.of(args[0].trim()));
|
configPaths.add(Path.of(args[3].trim()));
|
||||||
}
|
}
|
||||||
ClientConfig clientConfig = Config.loadConfig(ClientConfig.class, configPaths, new ClientConfig(), "default-config.yaml");
|
ClientConfig clientConfig = Config.loadConfig(ClientConfig.class, configPaths, new ClientConfig(), "default-config.yaml");
|
||||||
Client client = new Client(clientConfig);
|
Client client = new Client(clientConfig, host, port, username);
|
||||||
client.run();
|
client.run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,13 +42,11 @@ public class CommunicationHandler {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void establishConnection() throws IOException {
|
public void establishConnection(String host, int port, String username) throws IOException {
|
||||||
if (socket != null && !socket.isClosed()) {
|
if (socket != null && !socket.isClosed()) {
|
||||||
socket.close();
|
socket.close();
|
||||||
}
|
}
|
||||||
InetAddress address = InetAddress.getByName(client.getConfig().serverHost);
|
InetAddress address = InetAddress.getByName(host);
|
||||||
int port = client.getConfig().serverPort;
|
|
||||||
String username = client.getConfig().username;
|
|
||||||
System.out.printf("Connecting to server at %s, port %d, with username \"%s\"...%n", address, port, username);
|
System.out.printf("Connecting to server at %s, port %d, with username \"%s\"...%n", address, port, username);
|
||||||
|
|
||||||
socket = new Socket(address, port);
|
socket = new Socket(address, port);
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
package nl.andrewl.aos2_client.config;
|
package nl.andrewl.aos2_client.config;
|
||||||
|
|
||||||
public class ClientConfig {
|
public class ClientConfig {
|
||||||
public String serverHost = "localhost";
|
|
||||||
public int serverPort = 25565;
|
|
||||||
public String username = "player";
|
|
||||||
public InputConfig input = new InputConfig();
|
public InputConfig input = new InputConfig();
|
||||||
public DisplayConfig display = new DisplayConfig();
|
public DisplayConfig display = new DisplayConfig();
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
# Ace of Shades 2 Client Configuration
|
# Ace of Shades 2 Client Configuration
|
||||||
# Set these properties to connect to a server.
|
|
||||||
serverHost: localhost
|
|
||||||
serverPort: 25565
|
|
||||||
username: player
|
|
||||||
|
|
||||||
# Settings for input.
|
# Settings for input.
|
||||||
input:
|
input:
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
function join_by {
|
||||||
|
local d=${1-} f=${2-}
|
||||||
|
if shift 2; then
|
||||||
|
printf %s "$f" "${@/#/$d}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
mvn clean package javafx:jlink -DskipDebug=true -DstripJavaDebugAttributes=true -DnoHeaderFiles=true -DnoManPages=true
|
||||||
|
|
||||||
|
cd target
|
||||||
|
module_jars=(lib/*)
|
||||||
|
eligible_main_jars=("*jar-with-dependencies.jar")
|
||||||
|
main_jar=(${eligible_main_jars[0]})
|
||||||
|
module_path=$(join_by ";" ${module_jars[@]})
|
||||||
|
module_path="$main_jar;$module_path"
|
||||||
|
|
||||||
|
jpackage \
|
||||||
|
--name "Ace of Shades Launcher" \
|
||||||
|
--app-version "1.0.0" \
|
||||||
|
--description "Launcher app for Ace of Shades, a voxel-based first-person shooter." \
|
||||||
|
--linux-shortcut \
|
||||||
|
--linux-deb-maintainer "andrewlalisofficial@gmail.com" \
|
||||||
|
--linux-menu-group "Game" \
|
||||||
|
--runtime-image image \
|
||||||
|
--main-jar $main_jar \
|
||||||
|
--main-class nl.andrewl.aos2_launcher.Launcher \
|
||||||
|
--input .
|
||||||
|
|
|
@ -11,8 +11,9 @@
|
||||||
<properties>
|
<properties>
|
||||||
<maven.compiler.source>18</maven.compiler.source>
|
<maven.compiler.source>18</maven.compiler.source>
|
||||||
<maven.compiler.target>18</maven.compiler.target>
|
<maven.compiler.target>18</maven.compiler.target>
|
||||||
<javafx.version>18.0.1</javafx.version>
|
<javafx.version>18.0.2</javafx.version>
|
||||||
<javafx.maven.plugin.version>0.0.8</javafx.maven.plugin.version>
|
<javafx.maven.plugin.version>0.0.8</javafx.maven.plugin.version>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
@ -53,6 +54,46 @@
|
||||||
<target>17</target>
|
<target>17</target>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<version>3.3.0</version>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<manifest>
|
||||||
|
<mainClass>nl.andrewl.aos2_launcher.Launcher</mainClass>
|
||||||
|
</manifest>
|
||||||
|
</archive>
|
||||||
|
<descriptorRefs>
|
||||||
|
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||||
|
</descriptorRefs>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>make-assembly</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>single</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-dependency-plugin</artifactId>
|
||||||
|
<version>2.8</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>copy-dependencies</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<outputDirectory>${project.build.directory}/lib</outputDirectory>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
package nl.andrewl.aos2_launcher;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.scene.control.Alert;
|
||||||
|
import javafx.stage.Window;
|
||||||
|
import nl.andrewl.aos2_launcher.model.Profile;
|
||||||
|
import nl.andrewl.aos2_launcher.model.ProgressReporter;
|
||||||
|
import nl.andrewl.aos2_launcher.model.Server;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public class GameRunner {
|
||||||
|
public void run(Profile profile, Server server, ProgressReporter progressReporter, Window owner) {
|
||||||
|
SystemVersionValidator.getJreExecutablePath(progressReporter)
|
||||||
|
.whenCompleteAsync((jrePath, throwable) -> {
|
||||||
|
if (throwable != null) {
|
||||||
|
showPopup(
|
||||||
|
owner,
|
||||||
|
Alert.AlertType.ERROR,
|
||||||
|
"An error occurred while ensuring that you've got the latest Java runtime: " + throwable.getMessage()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
VersionFetcher.INSTANCE.ensureVersionIsDownloaded(profile.getClientVersion(), progressReporter)
|
||||||
|
.whenCompleteAsync((clientJarPath, throwable2) -> {
|
||||||
|
progressReporter.disableProgress();
|
||||||
|
if (throwable2 != null) {
|
||||||
|
showPopup(
|
||||||
|
owner,
|
||||||
|
Alert.AlertType.ERROR,
|
||||||
|
"An error occurred while ensuring you've got the correct client version: " + throwable2.getMessage()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
startGame(owner, profile, server, jrePath, clientJarPath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startGame(Window owner, Profile profile, Server server, Path jrePath, Path clientJarPath) {
|
||||||
|
try {
|
||||||
|
Process p = new ProcessBuilder()
|
||||||
|
.command(
|
||||||
|
jrePath.toAbsolutePath().toString(),
|
||||||
|
"-jar", clientJarPath.toAbsolutePath().toString(),
|
||||||
|
server.getHost(),
|
||||||
|
Integer.toString(server.getPort()),
|
||||||
|
profile.getUsername()
|
||||||
|
)
|
||||||
|
.directory(profile.getDir().toFile())
|
||||||
|
.inheritIO()
|
||||||
|
.start();
|
||||||
|
p.wait();
|
||||||
|
} catch (IOException e) {
|
||||||
|
showPopup(owner, Alert.AlertType.ERROR, "An error occurred while starting the game: " + e.getMessage());
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
showPopup(owner, Alert.AlertType.ERROR, "The game was interrupted: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showPopup(Window owner, Alert.AlertType type, String text) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||||
|
alert.initOwner(owner);
|
||||||
|
alert.setContentText(text);
|
||||||
|
alert.show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,23 +17,21 @@ public class Launcher extends Application {
|
||||||
public static final Path BASE_DIR = Path.of(System.getProperty("user.home"), ".ace-of-shades");
|
public static final Path BASE_DIR = Path.of(System.getProperty("user.home"), ".ace-of-shades");
|
||||||
public static final Path VERSIONS_DIR = BASE_DIR.resolve("versions");
|
public static final Path VERSIONS_DIR = BASE_DIR.resolve("versions");
|
||||||
public static final Path PROFILES_FILE = BASE_DIR.resolve("profiles.json");
|
public static final Path PROFILES_FILE = BASE_DIR.resolve("profiles.json");
|
||||||
|
public static final Path PROFILES_DIR = BASE_DIR.resolve("profiles");
|
||||||
public static final Path JRE_PATH = BASE_DIR.resolve("jre");
|
public static final Path JRE_PATH = BASE_DIR.resolve("jre");
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(Stage stage) throws IOException {
|
public void start(Stage stage) throws IOException {
|
||||||
if (!Files.exists(BASE_DIR)) {
|
if (!Files.exists(BASE_DIR)) Files.createDirectory(BASE_DIR);
|
||||||
Files.createDirectory(BASE_DIR);
|
if (!Files.exists(VERSIONS_DIR)) Files.createDirectory(VERSIONS_DIR);
|
||||||
}
|
if (!Files.exists(PROFILES_DIR)) Files.createDirectory(PROFILES_DIR);
|
||||||
if (!Files.exists(VERSIONS_DIR)) {
|
|
||||||
Files.createDirectory(VERSIONS_DIR);
|
|
||||||
}
|
|
||||||
FXMLLoader loader = new FXMLLoader(Launcher.class.getResource("/main_view.fxml"));
|
FXMLLoader loader = new FXMLLoader(Launcher.class.getResource("/main_view.fxml"));
|
||||||
Parent rootNode = loader.load();
|
Parent rootNode = loader.load();
|
||||||
Scene scene = new Scene(rootNode);
|
Scene scene = new Scene(rootNode);
|
||||||
addStylesheet(scene, "/font/fonts.css");
|
addStylesheet(scene, "/font/fonts.css");
|
||||||
addStylesheet(scene, "/styles.css");
|
addStylesheet(scene, "/styles.css");
|
||||||
stage.setScene(scene);
|
stage.setScene(scene);
|
||||||
stage.setTitle("Ace of Shades 2 - Launcher");
|
stage.setTitle("Ace of Shades - Launcher");
|
||||||
stage.show();
|
stage.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,29 +2,22 @@ package nl.andrewl.aos2_launcher;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.binding.BooleanBinding;
|
import javafx.beans.binding.BooleanBinding;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.collections.ListChangeListener;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import javafx.css.PseudoClass;
|
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.Node;
|
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.ProgressBar;
|
import javafx.scene.control.ProgressBar;
|
||||||
import javafx.scene.control.ProgressIndicator;
|
import javafx.scene.control.ProgressIndicator;
|
||||||
import javafx.scene.input.MouseEvent;
|
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import nl.andrewl.aos2_launcher.model.Profile;
|
import nl.andrewl.aos2_launcher.model.Profile;
|
||||||
import nl.andrewl.aos2_launcher.model.ProfileSet;
|
import nl.andrewl.aos2_launcher.model.ProfileSet;
|
||||||
import nl.andrewl.aos2_launcher.model.ProgressReporter;
|
import nl.andrewl.aos2_launcher.model.ProgressReporter;
|
||||||
import nl.andrewl.aos2_launcher.model.Server;
|
import nl.andrewl.aos2_launcher.model.Server;
|
||||||
import nl.andrewl.aos2_launcher.view.BindingUtil;
|
|
||||||
import nl.andrewl.aos2_launcher.view.EditProfileDialog;
|
import nl.andrewl.aos2_launcher.view.EditProfileDialog;
|
||||||
|
import nl.andrewl.aos2_launcher.view.ElementList;
|
||||||
import nl.andrewl.aos2_launcher.view.ProfileView;
|
import nl.andrewl.aos2_launcher.view.ProfileView;
|
||||||
import nl.andrewl.aos2_launcher.view.ServerView;
|
import nl.andrewl.aos2_launcher.view.ServerView;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
public class MainViewController implements ProgressReporter {
|
public class MainViewController implements ProgressReporter {
|
||||||
|
@ -32,80 +25,42 @@ public class MainViewController implements ProgressReporter {
|
||||||
@FXML public Button editProfileButton;
|
@FXML public Button editProfileButton;
|
||||||
@FXML public Button removeProfileButton;
|
@FXML public Button removeProfileButton;
|
||||||
@FXML public VBox profilesVBox;
|
@FXML public VBox profilesVBox;
|
||||||
|
private ElementList<Profile, ProfileView> profilesList;
|
||||||
@FXML public VBox serversVBox;
|
@FXML public VBox serversVBox;
|
||||||
@FXML public Label selectedProfileLabel;
|
private ElementList<Server, ServerView> serversList;
|
||||||
@FXML public Label selectedServerLabel;
|
|
||||||
|
|
||||||
@FXML public VBox progressVBox;
|
@FXML public VBox progressVBox;
|
||||||
@FXML public Label progressLabel;
|
@FXML public Label progressLabel;
|
||||||
@FXML public ProgressBar progressBar;
|
@FXML public ProgressBar progressBar;
|
||||||
|
|
||||||
private final ProfileSet profileSet = new ProfileSet();
|
private final ProfileSet profileSet = new ProfileSet();
|
||||||
private final ObservableList<Server> servers = FXCollections.observableArrayList();
|
|
||||||
private final ObjectProperty<Server> selectedServer = new SimpleObjectProperty<>(null);
|
|
||||||
|
|
||||||
private final ServersFetcher serversFetcher = new ServersFetcher();
|
private final ServersFetcher serversFetcher = new ServersFetcher();
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
BindingUtil.mapContent(serversVBox.getChildren(), servers, ServerView::new);
|
profilesList = new ElementList<>(profilesVBox, ProfileView::new, ProfileView.class, ProfileView::getProfile);
|
||||||
BindingUtil.mapContent(profilesVBox.getChildren(), profileSet.getProfiles(), ProfileView::new);
|
profileSet.selectedProfileProperty().addListener((observable, oldValue, newValue) -> profileSet.save());
|
||||||
profileSet.selectedProfileProperty().addListener((observable, oldValue, newValue) -> {
|
// A hack since we can't bind the profilesList's elements to the profileSet's.
|
||||||
if (newValue == null) {
|
profileSet.getProfiles().addListener((ListChangeListener<? super Profile>) c -> {
|
||||||
selectedProfileLabel.setText("None");
|
var selected = profileSet.getSelectedProfile();
|
||||||
} else {
|
profilesList.clear();
|
||||||
selectedProfileLabel.setText(newValue.getName());
|
profilesList.addAll(profileSet.getProfiles());
|
||||||
}
|
profilesList.selectElement(selected);
|
||||||
});
|
});
|
||||||
selectedServer.addListener((observable, oldValue, newValue) -> {
|
profileSet.loadOrCreateStandardFile();
|
||||||
if (newValue == null) {
|
profilesList.selectElement(profileSet.getSelectedProfile());
|
||||||
selectedServerLabel.setText("None");
|
profileSet.selectedProfileProperty().bind(profilesList.selectedElementProperty());
|
||||||
} else {
|
|
||||||
selectedServerLabel.setText(newValue.getName());
|
serversList = new ElementList<>(serversVBox, ServerView::new, ServerView.class, ServerView::getServer);
|
||||||
}
|
|
||||||
});
|
BooleanBinding playBind = profileSet.selectedProfileProperty().isNull().or(serversList.selectedElementProperty().isNull());
|
||||||
BooleanBinding playBind = profileSet.selectedProfileProperty().isNull().or(selectedServer.isNull());
|
|
||||||
playButton.disableProperty().bind(playBind);
|
playButton.disableProperty().bind(playBind);
|
||||||
editProfileButton.disableProperty().bind(profileSet.selectedProfileProperty().isNull());
|
editProfileButton.disableProperty().bind(profileSet.selectedProfileProperty().isNull());
|
||||||
removeProfileButton.disableProperty().bind(profileSet.selectedProfileProperty().isNull());
|
removeProfileButton.disableProperty().bind(profileSet.selectedProfileProperty().isNull());
|
||||||
|
|
||||||
profilesVBox.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
|
|
||||||
Node target = (Node) event.getTarget();
|
|
||||||
while (target != null) {
|
|
||||||
if (target instanceof ProfileView view) {
|
|
||||||
if (view.getProfile().equals(profileSet.getSelectedProfile()) && event.isControlDown()) {
|
|
||||||
selectProfile(null);
|
|
||||||
} else if (!event.isControlDown() && event.getClickCount() == 2) {
|
|
||||||
selectProfile(view);
|
|
||||||
editProfile();
|
|
||||||
} else {
|
|
||||||
selectProfile(view);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
target = target.getParent();
|
|
||||||
}
|
|
||||||
selectProfile(null);
|
|
||||||
});
|
|
||||||
serversVBox.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
|
|
||||||
Node target = (Node) event.getTarget();
|
|
||||||
while (target != null) {
|
|
||||||
if (target instanceof ServerView view) {
|
|
||||||
if (view.getServer().equals(selectedServer.get()) && event.isControlDown()) {
|
|
||||||
selectServer(null);
|
|
||||||
} else {
|
|
||||||
selectServer(view);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
target = target.getParent();
|
|
||||||
}
|
|
||||||
selectServer(null);
|
|
||||||
});
|
|
||||||
progressVBox.managedProperty().bind(progressVBox.visibleProperty());
|
progressVBox.managedProperty().bind(progressVBox.visibleProperty());
|
||||||
progressVBox.setVisible(false);
|
progressVBox.setVisible(false);
|
||||||
profileSet.loadOrCreateStandardFile();
|
|
||||||
updateProfileViewSelectedClass();
|
|
||||||
refreshServers();
|
refreshServers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,8 +72,8 @@ public class MainViewController implements ProgressReporter {
|
||||||
return new ArrayList<>();
|
return new ArrayList<>();
|
||||||
})
|
})
|
||||||
.thenAccept(newServers -> Platform.runLater(() -> {
|
.thenAccept(newServers -> Platform.runLater(() -> {
|
||||||
this.servers.clear();
|
serversList.clear();
|
||||||
this.servers.addAll(newServers);
|
serversList.addAll(newServers);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,58 +97,12 @@ public class MainViewController implements ProgressReporter {
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void play() {
|
public void play() {
|
||||||
Profile profile = profileSet.getSelectedProfile();
|
new GameRunner().run(
|
||||||
Server server = this.selectedServer.get();
|
profileSet.getSelectedProfile(),
|
||||||
SystemVersionValidator.getJreExecutablePath(this)
|
serversList.getSelectedElement(),
|
||||||
.thenAccept(jrePath -> {
|
this,
|
||||||
VersionFetcher.INSTANCE.ensureVersionIsDownloaded(profile.getClientVersion(), this)
|
this.profilesVBox.getScene().getWindow()
|
||||||
.thenAccept(clientJarPath -> {
|
);
|
||||||
try {
|
|
||||||
Process p = new ProcessBuilder()
|
|
||||||
.command(
|
|
||||||
jrePath.toAbsolutePath().toString(),
|
|
||||||
"-jar",
|
|
||||||
clientJarPath.toAbsolutePath().toString()
|
|
||||||
)
|
|
||||||
.directory(Launcher.BASE_DIR.toFile())
|
|
||||||
.inheritIO()
|
|
||||||
.start();
|
|
||||||
p.wait();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void selectProfile(ProfileView view) {
|
|
||||||
Profile profile = view == null ? null : view.getProfile();
|
|
||||||
profileSet.selectProfile(profile);
|
|
||||||
updateProfileViewSelectedClass();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateProfileViewSelectedClass() {
|
|
||||||
PseudoClass selectedClass = PseudoClass.getPseudoClass("selected");
|
|
||||||
for (var node : profilesVBox.getChildren()) {
|
|
||||||
ProfileView view = (ProfileView) node;
|
|
||||||
view.pseudoClassStateChanged(selectedClass, view.getProfile().equals(profileSet.getSelectedProfile()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void selectServer(ServerView view) {
|
|
||||||
Server server = view == null ? null : view.getServer();
|
|
||||||
selectedServer.set(server);
|
|
||||||
updateServerViewSelectedClass();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateServerViewSelectedClass() {
|
|
||||||
PseudoClass selectedClass = PseudoClass.getPseudoClass("selected");
|
|
||||||
for (var node : serversVBox.getChildren()) {
|
|
||||||
ServerView view = (ServerView) node;
|
|
||||||
view.pseudoClassStateChanged(selectedClass, view.getServer().equals(selectedServer.get()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -62,8 +62,8 @@ public class SystemVersionValidator {
|
||||||
progressReporter.setActionText("Downloading JRE...");
|
progressReporter.setActionText("Downloading JRE...");
|
||||||
HttpClient httpClient = HttpClient.newBuilder().followRedirects(HttpClient.Redirect.NORMAL).build();
|
HttpClient httpClient = HttpClient.newBuilder().followRedirects(HttpClient.Redirect.NORMAL).build();
|
||||||
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder().GET().timeout(Duration.ofMinutes(5));
|
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder().GET().timeout(Duration.ofMinutes(5));
|
||||||
String preferredJreName = getPreferredJreName();
|
String jreArchiveName = getPreferredJreName();
|
||||||
String url = JRE_DOWNLOAD_URL + preferredJreName;
|
String url = JRE_DOWNLOAD_URL + jreArchiveName;
|
||||||
HttpRequest req = requestBuilder.uri(URI.create(url)).build();
|
HttpRequest req = requestBuilder.uri(URI.create(url)).build();
|
||||||
return httpClient.sendAsync(req, HttpResponse.BodyHandlers.ofInputStream())
|
return httpClient.sendAsync(req, HttpResponse.BodyHandlers.ofInputStream())
|
||||||
.thenApplyAsync(resp -> {
|
.thenApplyAsync(resp -> {
|
||||||
|
@ -74,7 +74,7 @@ public class SystemVersionValidator {
|
||||||
FileUtils.deleteRecursive(Launcher.JRE_PATH);
|
FileUtils.deleteRecursive(Launcher.JRE_PATH);
|
||||||
}
|
}
|
||||||
Files.createDirectory(Launcher.JRE_PATH);
|
Files.createDirectory(Launcher.JRE_PATH);
|
||||||
Path jreArchiveFile = Launcher.JRE_PATH.resolve(preferredJreName);
|
Path jreArchiveFile = Launcher.JRE_PATH.resolve(jreArchiveName);
|
||||||
FileUtils.downloadWithProgress(jreArchiveFile, resp, progressReporter);
|
FileUtils.downloadWithProgress(jreArchiveFile, resp, progressReporter);
|
||||||
progressReporter.setProgress(-1); // Indefinite progress.
|
progressReporter.setProgress(-1); // Indefinite progress.
|
||||||
progressReporter.setActionText("Unpacking JRE...");
|
progressReporter.setActionText("Unpacking JRE...");
|
||||||
|
|
|
@ -38,8 +38,6 @@ public class VersionFetcher {
|
||||||
|
|
||||||
public VersionFetcher() {
|
public VersionFetcher() {
|
||||||
this.availableReleases = new ArrayList<>();
|
this.availableReleases = new ArrayList<>();
|
||||||
System.out.println(System.getProperty("os.name"));
|
|
||||||
System.out.println(System.getProperty("os.arch"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<ClientVersionRelease> getRelease(String versionTag) {
|
public CompletableFuture<ClientVersionRelease> getRelease(String versionTag) {
|
||||||
|
@ -130,13 +128,12 @@ public class VersionFetcher {
|
||||||
JsonObject assetObj = asset.getAsJsonObject();
|
JsonObject assetObj = asset.getAsJsonObject();
|
||||||
String name = assetObj.get("name").getAsString();
|
String name = assetObj.get("name").getAsString();
|
||||||
if (name.matches(regex)) {
|
if (name.matches(regex)) {
|
||||||
System.out.println("Found matching asset: " + name);
|
|
||||||
return assetObj;
|
return assetObj;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new RuntimeException("Couldn't find a matching release asset.");
|
throw new RuntimeException("Couldn't find a matching release asset for this system.");
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("Error while requesting release assets.");
|
throw new RuntimeException("Error while requesting release assets from GitHub: " + resp.statusCode());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return downloadUrlFuture.thenComposeAsync(asset -> {
|
return downloadUrlFuture.thenComposeAsync(asset -> {
|
||||||
|
@ -145,7 +142,6 @@ public class VersionFetcher {
|
||||||
HttpRequest downloadRequest = HttpRequest.newBuilder(URI.create(url))
|
HttpRequest downloadRequest = HttpRequest.newBuilder(URI.create(url))
|
||||||
.GET().timeout(Duration.ofMinutes(5)).build();
|
.GET().timeout(Duration.ofMinutes(5)).build();
|
||||||
Path file = Launcher.VERSIONS_DIR.resolve(fileName);
|
Path file = Launcher.VERSIONS_DIR.resolve(fileName);
|
||||||
System.out.printf("Downloading %s to %s.%n", fileName, file.toAbsolutePath());
|
|
||||||
return httpClient.sendAsync(downloadRequest, HttpResponse.BodyHandlers.ofInputStream())
|
return httpClient.sendAsync(downloadRequest, HttpResponse.BodyHandlers.ofInputStream())
|
||||||
.thenApplyAsync(resp -> {
|
.thenApplyAsync(resp -> {
|
||||||
if (resp.statusCode() == 200) {
|
if (resp.statusCode() == 200) {
|
||||||
|
@ -157,7 +153,7 @@ public class VersionFetcher {
|
||||||
}
|
}
|
||||||
return file;
|
return file;
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("Version download failed.");
|
throw new RuntimeException("Error while downloading release asset from GitHub: " + resp.statusCode());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,24 +2,28 @@ package nl.andrewl.aos2_launcher.model;
|
||||||
|
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.StringProperty;
|
||||||
|
import nl.andrewl.aos2_launcher.Launcher;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class Profile {
|
public class Profile {
|
||||||
private final UUID id;
|
private final UUID id;
|
||||||
private final StringProperty name;
|
private final StringProperty name;
|
||||||
private final StringProperty description;
|
private final StringProperty username;
|
||||||
private final StringProperty clientVersion;
|
private final StringProperty clientVersion;
|
||||||
|
private final StringProperty jvmArgs;
|
||||||
|
|
||||||
public Profile() {
|
public Profile() {
|
||||||
this(UUID.randomUUID(), "", null, null);
|
this(UUID.randomUUID(), "", "Player", null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Profile(UUID id, String name, String description, String clientVersion) {
|
public Profile(UUID id, String name, String username, String clientVersion, String jvmArgs) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.name = new SimpleStringProperty(name);
|
this.name = new SimpleStringProperty(name);
|
||||||
this.description = new SimpleStringProperty(description);
|
this.username = new SimpleStringProperty(username);
|
||||||
this.clientVersion = new SimpleStringProperty(clientVersion);
|
this.clientVersion = new SimpleStringProperty(clientVersion);
|
||||||
|
this.jvmArgs = new SimpleStringProperty(jvmArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public UUID getId() {
|
public UUID getId() {
|
||||||
|
@ -34,12 +38,12 @@ public class Profile {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDescription() {
|
public String getUsername() {
|
||||||
return description.get();
|
return username.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringProperty descriptionProperty() {
|
public StringProperty usernameProperty() {
|
||||||
return description;
|
return username;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getClientVersion() {
|
public String getClientVersion() {
|
||||||
|
@ -50,15 +54,31 @@ public class Profile {
|
||||||
return clientVersion;
|
return clientVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getJvmArgs() {
|
||||||
|
return jvmArgs.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringProperty jvmArgsProperty() {
|
||||||
|
return jvmArgs;
|
||||||
|
}
|
||||||
|
|
||||||
public void setName(String name) {
|
public void setName(String name) {
|
||||||
this.name.set(name);
|
this.name.set(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDescription(String description) {
|
public void setUsername(String username) {
|
||||||
this.description.set(description);
|
this.username.set(username);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setClientVersion(String clientVersion) {
|
public void setClientVersion(String clientVersion) {
|
||||||
this.clientVersion.set(clientVersion);
|
this.clientVersion.set(clientVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setJvmArgs(String jvmArgs) {
|
||||||
|
this.jvmArgs.set(jvmArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getDir() {
|
||||||
|
return Launcher.PROFILES_DIR.resolve(id.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import nl.andrewl.aos2_launcher.Launcher;
|
import nl.andrewl.aos2_launcher.Launcher;
|
||||||
|
import nl.andrewl.aos2_launcher.util.FileUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
@ -32,16 +33,26 @@ public class ProfileSet {
|
||||||
|
|
||||||
public void addNewProfile(Profile profile) {
|
public void addNewProfile(Profile profile) {
|
||||||
profiles.add(profile);
|
profiles.add(profile);
|
||||||
selectedProfile.set(profile);
|
|
||||||
save();
|
save();
|
||||||
|
try {
|
||||||
|
if (!Files.exists(profile.getDir())) {
|
||||||
|
Files.createDirectory(profile.getDir());
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeProfile(Profile profile) {
|
public void removeProfile(Profile profile) {
|
||||||
if (profile == null) return;
|
if (profile == null) return;
|
||||||
boolean removed = profiles.remove(profile);
|
boolean removed = profiles.remove(profile);
|
||||||
if (removed) {
|
if (removed) {
|
||||||
if (selectedProfile.get() != null && selectedProfile.get().equals(profile)) {
|
try {
|
||||||
selectedProfile.set(null);
|
if (Files.exists(profile.getDir())) {
|
||||||
|
FileUtils.deleteRecursive(profile.getDir());
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
save();
|
save();
|
||||||
}
|
}
|
||||||
|
@ -51,25 +62,27 @@ public class ProfileSet {
|
||||||
removeProfile(getSelectedProfile());
|
removeProfile(getSelectedProfile());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void selectProfile(Profile profile) {
|
|
||||||
if (!profiles.contains(profile)) return;
|
|
||||||
selectedProfile.set(profile);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void load(Path file) throws IOException {
|
public void load(Path file) throws IOException {
|
||||||
try (var reader = Files.newBufferedReader(file)) {
|
try (var reader = Files.newBufferedReader(file)) {
|
||||||
JsonObject data = new Gson().fromJson(reader, JsonObject.class);
|
JsonObject data = new Gson().fromJson(reader, JsonObject.class);
|
||||||
profiles.clear();
|
profiles.clear();
|
||||||
JsonElement selectedProfileIdElement = data.get("selectedProfileId");
|
JsonElement selectedProfileIdElement = data.get("selectedProfileId");
|
||||||
UUID selectedProfileId = selectedProfileIdElement.isJsonNull() ? null : UUID.fromString(selectedProfileIdElement.getAsString());
|
UUID selectedProfileId = (selectedProfileIdElement == null || selectedProfileIdElement.isJsonNull())
|
||||||
|
? null
|
||||||
|
: UUID.fromString(selectedProfileIdElement.getAsString());
|
||||||
JsonArray profilesArray = data.getAsJsonArray("profiles");
|
JsonArray profilesArray = data.getAsJsonArray("profiles");
|
||||||
for (JsonElement element : profilesArray) {
|
for (JsonElement element : profilesArray) {
|
||||||
JsonObject profileObj = element.getAsJsonObject();
|
JsonObject profileObj = element.getAsJsonObject();
|
||||||
UUID id = UUID.fromString(profileObj.get("id").getAsString());
|
UUID id = UUID.fromString(profileObj.get("id").getAsString());
|
||||||
String name = profileObj.get("name").getAsString();
|
String name = profileObj.get("name").getAsString();
|
||||||
String description = profileObj.get("description").getAsString();
|
|
||||||
String clientVersion = profileObj.get("clientVersion").getAsString();
|
String clientVersion = profileObj.get("clientVersion").getAsString();
|
||||||
Profile profile = new Profile(id, name, description, clientVersion);
|
String username = profileObj.get("username").getAsString();
|
||||||
|
JsonElement jvmArgsElement = profileObj.get("jvmArgs");
|
||||||
|
String jvmArgs = null;
|
||||||
|
if (jvmArgsElement != null && jvmArgsElement.isJsonPrimitive() && jvmArgsElement.getAsJsonPrimitive().isString()) {
|
||||||
|
jvmArgs = jvmArgsElement.getAsString();
|
||||||
|
}
|
||||||
|
Profile profile = new Profile(id, name, username, clientVersion, jvmArgs);
|
||||||
profiles.add(profile);
|
profiles.add(profile);
|
||||||
if (selectedProfileId != null && selectedProfileId.equals(profile.getId())) {
|
if (selectedProfileId != null && selectedProfileId.equals(profile.getId())) {
|
||||||
selectedProfile.set(profile);
|
selectedProfile.set(profile);
|
||||||
|
@ -106,8 +119,9 @@ public class ProfileSet {
|
||||||
JsonObject obj = new JsonObject();
|
JsonObject obj = new JsonObject();
|
||||||
obj.addProperty("id", profile.getId().toString());
|
obj.addProperty("id", profile.getId().toString());
|
||||||
obj.addProperty("name", profile.getName());
|
obj.addProperty("name", profile.getName());
|
||||||
obj.addProperty("description", profile.getDescription());
|
obj.addProperty("username", profile.getUsername());
|
||||||
obj.addProperty("clientVersion", profile.getClientVersion());
|
obj.addProperty("clientVersion", profile.getClientVersion());
|
||||||
|
obj.addProperty("jvmArgs", profile.getJvmArgs());
|
||||||
profilesArray.add(obj);
|
profilesArray.add(obj);
|
||||||
}
|
}
|
||||||
data.add("profiles", profilesArray);
|
data.add("profiles", profilesArray);
|
||||||
|
@ -122,7 +136,7 @@ public class ProfileSet {
|
||||||
try {
|
try {
|
||||||
save(lastFileUsed);
|
save(lastFileUsed);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,12 +19,10 @@ import java.io.IOException;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public class EditProfileDialog extends Dialog<Profile> {
|
public class EditProfileDialog extends Dialog<Profile> {
|
||||||
@FXML
|
@FXML public TextField nameField;
|
||||||
public TextField nameField;
|
@FXML public TextField usernameField;
|
||||||
@FXML
|
@FXML public ChoiceBox<String> clientVersionChoiceBox;
|
||||||
public TextArea descriptionTextArea;
|
@FXML public TextArea jvmArgsTextArea;
|
||||||
@FXML
|
|
||||||
public ChoiceBox<String> clientVersionChoiceBox;
|
|
||||||
|
|
||||||
private final ObjectProperty<Profile> profile;
|
private final ObjectProperty<Profile> profile;
|
||||||
|
|
||||||
|
@ -40,18 +38,23 @@ public class EditProfileDialog extends Dialog<Profile> {
|
||||||
setTitle("Edit Profile");
|
setTitle("Edit Profile");
|
||||||
|
|
||||||
BooleanBinding formInvalid = nameField.textProperty().isEmpty()
|
BooleanBinding formInvalid = nameField.textProperty().isEmpty()
|
||||||
.or(clientVersionChoiceBox.valueProperty().isNull());
|
.or(clientVersionChoiceBox.valueProperty().isNull())
|
||||||
|
.or(usernameField.textProperty().isEmpty());
|
||||||
nameField.setText(profile.getName());
|
nameField.setText(profile.getName());
|
||||||
descriptionTextArea.setText(profile.getDescription());
|
usernameField.setText(profile.getUsername());
|
||||||
VersionFetcher.INSTANCE.getAvailableReleases().thenAccept(releases -> {
|
VersionFetcher.INSTANCE.getAvailableReleases().thenAccept(releases -> Platform.runLater(() -> {
|
||||||
Platform.runLater(() -> {
|
|
||||||
clientVersionChoiceBox.setItems(FXCollections.observableArrayList(releases.stream().map(ClientVersionRelease::tag).toList()));
|
clientVersionChoiceBox.setItems(FXCollections.observableArrayList(releases.stream().map(ClientVersionRelease::tag).toList()));
|
||||||
|
// If the profile doesn't have a set version, use the latest release.
|
||||||
|
if (profile.getClientVersion() == null || profile.getClientVersion().isBlank()) {
|
||||||
String lastRelease = releases.size() == 0 ? null : releases.get(0).tag();
|
String lastRelease = releases.size() == 0 ? null : releases.get(0).tag();
|
||||||
if (lastRelease != null) {
|
if (lastRelease != null) {
|
||||||
clientVersionChoiceBox.setValue(lastRelease);
|
clientVersionChoiceBox.setValue(lastRelease);
|
||||||
}
|
}
|
||||||
});
|
} else {
|
||||||
});
|
clientVersionChoiceBox.setValue(profile.getClientVersion());
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
jvmArgsTextArea.setText(profile.getJvmArgs());
|
||||||
|
|
||||||
DialogPane pane = new DialogPane();
|
DialogPane pane = new DialogPane();
|
||||||
pane.setContent(parent);
|
pane.setContent(parent);
|
||||||
|
@ -67,13 +70,9 @@ public class EditProfileDialog extends Dialog<Profile> {
|
||||||
}
|
}
|
||||||
var prof = this.profile.getValue();
|
var prof = this.profile.getValue();
|
||||||
prof.setName(nameField.getText().trim());
|
prof.setName(nameField.getText().trim());
|
||||||
String descriptionText = descriptionTextArea.getText().trim();
|
prof.setUsername(usernameField.getText().trim());
|
||||||
if (descriptionText.isBlank()) {
|
|
||||||
prof.setDescription(null);
|
|
||||||
} else {
|
|
||||||
prof.setDescription(descriptionText);
|
|
||||||
}
|
|
||||||
prof.setClientVersion(clientVersionChoiceBox.getValue());
|
prof.setClientVersion(clientVersionChoiceBox.getValue());
|
||||||
|
prof.setJvmArgs(jvmArgsTextArea.getText());
|
||||||
return this.profile.getValue();
|
return this.profile.getValue();
|
||||||
});
|
});
|
||||||
setOnShowing(event -> Platform.runLater(() -> nameField.requestFocus()));
|
setOnShowing(event -> Platform.runLater(() -> nameField.requestFocus()));
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
package nl.andrewl.aos2_launcher.view;
|
||||||
|
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.css.PseudoClass;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.input.MouseEvent;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class ElementList<T, V extends Node> {
|
||||||
|
private final Pane container;
|
||||||
|
|
||||||
|
private final ObjectProperty<T> selectedElement = new SimpleObjectProperty<>(null);
|
||||||
|
private final ObservableList<T> elements = FXCollections.observableArrayList();
|
||||||
|
private final Class<V> elementViewType;
|
||||||
|
private final Function<V, T> viewElementMapper;
|
||||||
|
|
||||||
|
public ElementList(
|
||||||
|
Pane container,
|
||||||
|
Function<T, V> elementViewMapper,
|
||||||
|
Class<V> elementViewType,
|
||||||
|
Function<V, T> viewElementMapper
|
||||||
|
) {
|
||||||
|
this.container = container;
|
||||||
|
this.elementViewType = elementViewType;
|
||||||
|
this.viewElementMapper = viewElementMapper;
|
||||||
|
BindingUtil.mapContent(container.getChildren(), elements, element -> {
|
||||||
|
V view = elementViewMapper.apply(element);
|
||||||
|
view.getStyleClass().add("element-list-item");
|
||||||
|
return view;
|
||||||
|
});
|
||||||
|
container.addEventHandler(MouseEvent.MOUSE_CLICKED, this::handleMouseClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private void handleMouseClick(MouseEvent event) {
|
||||||
|
Node target = (Node) event.getTarget();
|
||||||
|
while (target != null) {
|
||||||
|
if (target.getClass().equals(elementViewType)) {
|
||||||
|
V elementView = (V) target;
|
||||||
|
T targetElement = viewElementMapper.apply(elementView);
|
||||||
|
if (event.isControlDown()) {
|
||||||
|
if (selectedElement.get() == null) {
|
||||||
|
selectElement(targetElement);
|
||||||
|
} else {
|
||||||
|
selectElement(null);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
selectElement(targetElement);
|
||||||
|
}
|
||||||
|
return; // Exit since we found a valid target.
|
||||||
|
}
|
||||||
|
target = target.getParent();
|
||||||
|
}
|
||||||
|
selectElement(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void selectElement(T element) {
|
||||||
|
if (element != null && !elements.contains(element)) return;
|
||||||
|
selectedElement.set(element);
|
||||||
|
updateSelectedPseudoClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private void updateSelectedPseudoClass() {
|
||||||
|
PseudoClass selectedClass = PseudoClass.getPseudoClass("selected");
|
||||||
|
for (var node : container.getChildren()) {
|
||||||
|
if (!node.getClass().equals(elementViewType)) continue;
|
||||||
|
V view = (V) node;
|
||||||
|
T thisElement = viewElementMapper.apply(view);
|
||||||
|
view.pseudoClassStateChanged(selectedClass, thisElement.equals(selectedElement.get()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getSelectedElement() {
|
||||||
|
return selectedElement.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectProperty<T> selectedElementProperty() {
|
||||||
|
return selectedElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableList<T> getElements() {
|
||||||
|
return elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
elements.clear();
|
||||||
|
selectElement(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(T element) {
|
||||||
|
elements.add(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addAll(Collection<T> newElements) {
|
||||||
|
elements.addAll(newElements);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove(T element) {
|
||||||
|
elements.remove(element);
|
||||||
|
if (element != null && element.equals(selectedElement.get())) {
|
||||||
|
selectElement(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,22 +1,35 @@
|
||||||
package nl.andrewl.aos2_launcher.view;
|
package nl.andrewl.aos2_launcher.view;
|
||||||
|
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.Pane;
|
||||||
import nl.andrewl.aos2_launcher.model.Profile;
|
import nl.andrewl.aos2_launcher.model.Profile;
|
||||||
|
|
||||||
public class ProfileView extends VBox {
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class ProfileView extends Pane {
|
||||||
private final Profile profile;
|
private final Profile profile;
|
||||||
|
|
||||||
|
@FXML public Label nameLabel;
|
||||||
|
@FXML public Label clientVersionLabel;
|
||||||
|
@FXML public Label usernameLabel;
|
||||||
|
|
||||||
public ProfileView(Profile profile) {
|
public ProfileView(Profile profile) {
|
||||||
this.profile = profile;
|
this.profile = profile;
|
||||||
var nameLabel = new Label();
|
|
||||||
|
try {
|
||||||
|
FXMLLoader loader = new FXMLLoader(ProfileView.class.getResource("/profile_view.fxml"));
|
||||||
|
loader.setController(this);
|
||||||
|
Node node = loader.load();
|
||||||
|
getChildren().add(node);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
nameLabel.textProperty().bind(profile.nameProperty());
|
nameLabel.textProperty().bind(profile.nameProperty());
|
||||||
var descriptionLabel = new Label();
|
clientVersionLabel.textProperty().bind(profile.clientVersionProperty());
|
||||||
descriptionLabel.textProperty().bind(profile.descriptionProperty());
|
usernameLabel.textProperty().bind(profile.usernameProperty());
|
||||||
var versionLabel = new Label();
|
|
||||||
versionLabel.textProperty().bind(profile.clientVersionProperty());
|
|
||||||
getChildren().addAll(nameLabel, descriptionLabel, versionLabel);
|
|
||||||
getStyleClass().add("list-item");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Profile getProfile() {
|
public Profile getProfile() {
|
||||||
|
|
|
@ -10,16 +10,20 @@
|
||||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||||
</padding>
|
</padding>
|
||||||
<AnchorPane VBox.vgrow="NEVER">
|
<AnchorPane VBox.vgrow="NEVER">
|
||||||
<Label text="Name" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0" />
|
<Label text="Profile Name" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0" />
|
||||||
<TextField fx:id="nameField" promptText="Enter a name for the profile..." AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="150.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
|
<TextField fx:id="nameField" promptText="Enter a name for the profile..." AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="150.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
|
||||||
</AnchorPane>
|
</AnchorPane>
|
||||||
<AnchorPane VBox.vgrow="NEVER">
|
<AnchorPane VBox.vgrow="NEVER">
|
||||||
<Label text="Description" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0" />
|
<Label text="Username" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0" />
|
||||||
<TextArea fx:id="descriptionTextArea" prefHeight="100.0" prefWidth="200.0" promptText="Add a description for this profile..." wrapText="true" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="150.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
|
<TextField fx:id="usernameField" promptText="Enter a username..." AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="150.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
|
||||||
</AnchorPane>
|
</AnchorPane>
|
||||||
<AnchorPane VBox.vgrow="NEVER">
|
<AnchorPane VBox.vgrow="NEVER">
|
||||||
<Label text="Client Version" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0" />
|
<Label text="Client Version" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0" />
|
||||||
<ChoiceBox fx:id="clientVersionChoiceBox" prefWidth="150.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="150.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
|
<ChoiceBox fx:id="clientVersionChoiceBox" prefWidth="150.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="150.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
|
||||||
</AnchorPane>
|
</AnchorPane>
|
||||||
|
<AnchorPane>
|
||||||
|
<Label text="JVM Arguments" wrapText="true" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0"/>
|
||||||
|
<TextArea fx:id="jvmArgsTextArea" prefHeight="100.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="150.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"/>
|
||||||
|
</AnchorPane>
|
||||||
</VBox>
|
</VBox>
|
||||||
</AnchorPane>
|
</AnchorPane>
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<?import javafx.geometry.*?>
|
<?import javafx.geometry.Insets?>
|
||||||
<?import javafx.scene.control.*?>
|
<?import javafx.scene.control.*?>
|
||||||
<?import javafx.scene.layout.*?>
|
<?import javafx.scene.layout.*?>
|
||||||
<?import javafx.scene.text.*?>
|
<?import javafx.scene.text.Font?>
|
||||||
|
<VBox minHeight="300.0" minWidth="300.0" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nl.andrewl.aos2_launcher.MainViewController">
|
||||||
<VBox maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="300.0" minWidth="300.0" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nl.andrewl.aos2_launcher.MainViewController">
|
|
||||||
<TabPane tabClosingPolicy="UNAVAILABLE" VBox.vgrow="ALWAYS">
|
<TabPane tabClosingPolicy="UNAVAILABLE" VBox.vgrow="ALWAYS">
|
||||||
<Tab text="Profiles">
|
<Tab text="Profiles">
|
||||||
<VBox>
|
<VBox>
|
||||||
|
@ -31,31 +30,26 @@
|
||||||
</Tab>
|
</Tab>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
<HBox alignment="CENTER" styleClass="button-bar" VBox.vgrow="NEVER">
|
<HBox alignment="CENTER" styleClass="button-bar" VBox.vgrow="NEVER">
|
||||||
<HBox alignment="CENTER_LEFT" spacing="5" HBox.hgrow="NEVER">
|
<Button
|
||||||
<Label text="Profile: " />
|
fx:id="playButton"
|
||||||
<Label fx:id="selectedProfileLabel" text="None" />
|
mnemonicParsing="false"
|
||||||
</HBox>
|
onAction="#play"
|
||||||
<Button fx:id="playButton" mnemonicParsing="false" onAction="#play" text="Play" />
|
text="Play"
|
||||||
<HBox alignment="CENTER_LEFT" spacing="5" HBox.hgrow="NEVER">
|
/>
|
||||||
<Label text="Server: " />
|
|
||||||
<Label fx:id="selectedServerLabel" text="None" />
|
|
||||||
</HBox>
|
|
||||||
</HBox>
|
</HBox>
|
||||||
<VBox fx:id="progressVBox" VBox.vgrow="NEVER">
|
<VBox fx:id="progressVBox" VBox.vgrow="NEVER">
|
||||||
<children>
|
|
||||||
<AnchorPane VBox.vgrow="NEVER">
|
<AnchorPane VBox.vgrow="NEVER">
|
||||||
<children>
|
<padding>
|
||||||
<Label fx:id="progressLabel" text="Work in progress..." AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0">
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0"/>
|
||||||
|
</padding>
|
||||||
|
<Label fx:id="progressLabel" text="Work in progress..." AnchorPane.bottomAnchor="0.0"
|
||||||
|
AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||||
<font>
|
<font>
|
||||||
<Font size="10.0" />
|
<Font size="10.0"/>
|
||||||
</font>
|
</font>
|
||||||
</Label>
|
</Label>
|
||||||
<ProgressBar fx:id="progressBar" prefWidth="200.0" progress="0.0" AnchorPane.bottomAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
|
<ProgressBar fx:id="progressBar" prefWidth="200.0" progress="0.0" AnchorPane.bottomAnchor="0.0"
|
||||||
</children>
|
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"/>
|
||||||
<padding>
|
|
||||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
|
||||||
</padding>
|
|
||||||
</AnchorPane>
|
</AnchorPane>
|
||||||
</children>
|
|
||||||
</VBox>
|
</VBox>
|
||||||
</VBox>
|
</VBox>
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.*?>
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
<BorderPane prefWidth="300.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
|
<padding><Insets top="5" bottom="5" left="5" right="5"/></padding>
|
||||||
|
<top>
|
||||||
|
<Label fx:id="nameLabel" text="Profile Name" BorderPane.alignment="CENTER_LEFT" style="-fx-font-size: 16px; -fx-font-weight: bold;">
|
||||||
|
<BorderPane.margin>
|
||||||
|
<Insets bottom="5.0" />
|
||||||
|
</BorderPane.margin>
|
||||||
|
</Label>
|
||||||
|
</top>
|
||||||
|
<center>
|
||||||
|
<VBox BorderPane.alignment="CENTER">
|
||||||
|
<AnchorPane VBox.vgrow="NEVER">
|
||||||
|
<Label text="Client Version" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
|
||||||
|
AnchorPane.topAnchor="0.0"/>
|
||||||
|
<Label fx:id="clientVersionLabel" text="v1.0.0" textAlignment="RIGHT" AnchorPane.bottomAnchor="0.0"
|
||||||
|
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" style="-fx-font-weight: bold;"/>
|
||||||
|
</AnchorPane>
|
||||||
|
<AnchorPane VBox.vgrow="NEVER">
|
||||||
|
<Label text="Username" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
|
||||||
|
AnchorPane.topAnchor="0.0"/>
|
||||||
|
<Label fx:id="usernameLabel" text="Player" textAlignment="RIGHT" AnchorPane.bottomAnchor="0.0"
|
||||||
|
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" style="-fx-font-weight: bold;"/>
|
||||||
|
</AnchorPane>
|
||||||
|
</VBox>
|
||||||
|
</center>
|
||||||
|
</BorderPane>
|
|
@ -13,7 +13,7 @@
|
||||||
-fx-spacing: 5;
|
-fx-spacing: 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-item:selected {
|
.element-list-item:selected {
|
||||||
-fx-background-color: #e3e3e3;
|
-fx-background-color: #e3e3e3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue