diff --git a/client/pom.xml b/client/pom.xml
index 0ec1214..1117e56 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -5,7 +5,7 @@
ace-of-shades
nl.andrewlalis
- 3.1
+ 4.0
4.0.0
diff --git a/client/src/main/java/nl/andrewlalis/aos_client/launcher/Launcher.java b/client/src/main/java/nl/andrewlalis/aos_client/launcher/Launcher.java
index 3fd3c47..7de48db 100644
--- a/client/src/main/java/nl/andrewlalis/aos_client/launcher/Launcher.java
+++ b/client/src/main/java/nl/andrewlalis/aos_client/launcher/Launcher.java
@@ -1,9 +1,7 @@
package nl.andrewlalis.aos_client.launcher;
import nl.andrewlalis.aos_client.Client;
-import nl.andrewlalis.aos_client.launcher.servers.ServerInfo;
-import nl.andrewlalis.aos_client.launcher.servers.ServerInfoCellRenderer;
-import nl.andrewlalis.aos_client.launcher.servers.ServerInfoListModel;
+import nl.andrewlalis.aos_client.launcher.servers.*;
import javax.swing.*;
import java.awt.*;
@@ -12,6 +10,7 @@ import java.awt.event.MouseEvent;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
@@ -22,7 +21,9 @@ import java.util.regex.Pattern;
* the menu that the user interacts with before joining a game.
*/
public class Launcher extends JFrame {
- private static final Pattern addressPattern = Pattern.compile("(.+):(\\d+)");
+ public static final Pattern addressPattern = Pattern.compile("(.+):(\\d+)");
+ public static final Path DATA_DIR = Path.of(System.getProperty("user.home"), "ace-of-shades");
+ public static final Pattern usernamePattern = Pattern.compile("[a-zA-Z0-9_-]+");
public Launcher() throws HeadlessException {
super("Ace of Shades - Launcher");
@@ -36,9 +37,6 @@ public class Launcher extends JFrame {
private Container buildContent() {
JTabbedPane mainPanel = new JTabbedPane(SwingConstants.TOP, JTabbedPane.SCROLL_TAB_LAYOUT);
mainPanel.addTab("Servers", null, this.getServersPanel(), "View a list of available servers.");
-//
-// JPanel settingsPanel = new JPanel();
-// mainPanel.addTab("Settings", null, settingsPanel, "Change game settings.");
return mainPanel;
}
@@ -47,8 +45,6 @@ public class Launcher extends JFrame {
JPanel panel = new JPanel(new BorderLayout());
ServerInfoListModel listModel = new ServerInfoListModel();
- listModel.add(new ServerInfo("one", "localhost:8035", "andrew"));
- listModel.add(new ServerInfo("two", "localhost:25565", null));
JList serversList = new JList<>(listModel);
serversList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
serversList.setCellRenderer(new ServerInfoCellRenderer());
@@ -68,8 +64,15 @@ public class Launcher extends JFrame {
menu.add(connectItem);
JMenuItem editItem = new JMenuItem("Edit");
editItem.addActionListener(a -> {
- // TODO: Open edit dialog.
- listModel.serverEdited();
+ EditServerDialog dialog = new EditServerDialog((Frame) SwingUtilities.getWindowAncestor(panel), server);
+ dialog.setVisible(true);
+ ServerInfo editedInfo = dialog.getServerInfo();
+ if (editedInfo != null) {
+ server.setName(editedInfo.getName());
+ server.setHost(editedInfo.getHost());
+ server.setUsername(editedInfo.getUsername());
+ listModel.serverEdited();
+ }
});
menu.add(editItem);
JMenuItem removeItem = new JMenuItem("Remove");
@@ -94,7 +97,12 @@ public class Launcher extends JFrame {
JButton addServerButton = new JButton("Add Server");
addServerButton.setToolTipText("Add a new server to the list.");
addServerButton.addActionListener(e -> {
- // TODO: Add server dialog.
+ AddServerDialog dialog = new AddServerDialog(this);
+ dialog.setVisible(true);
+ ServerInfo server = dialog.getServerInfo();
+ if (server != null) {
+ listModel.add(server);
+ }
});
buttonPanel.add(addServerButton);
JButton directConnectButton = new JButton("Direct Connect");
diff --git a/client/src/main/java/nl/andrewlalis/aos_client/launcher/servers/AddServerDialog.java b/client/src/main/java/nl/andrewlalis/aos_client/launcher/servers/AddServerDialog.java
new file mode 100644
index 0000000..59788eb
--- /dev/null
+++ b/client/src/main/java/nl/andrewlalis/aos_client/launcher/servers/AddServerDialog.java
@@ -0,0 +1,113 @@
+package nl.andrewlalis.aos_client.launcher.servers;
+
+import nl.andrewlalis.aos_client.launcher.Launcher;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.List;
+
+public class AddServerDialog extends JDialog {
+ protected JTextField serverNameField;
+ protected JTextField serverAddressField;
+ protected JTextField usernameField;
+
+ private ServerInfo serverInfo;
+
+ public AddServerDialog(Frame owner) {
+ super(owner, true);
+ this.setTitle("Add Server");
+ this.setContentPane(this.getContent());
+ this.pack();
+ this.setLocationRelativeTo(owner);
+ }
+
+ public ServerInfo getServerInfo() {
+ return this.serverInfo;
+ }
+
+ private Container getContent() {
+ JPanel container = new JPanel(new BorderLayout());
+ JPanel inputPanel = new JPanel(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+ c.insets = new Insets(5, 5, 5, 5);
+
+ c.gridx = 0;
+ c.gridy = 0;
+ inputPanel.add(new JLabel("Name"), c);
+ serverNameField = new JTextField(20);
+ c.gridx++;
+ inputPanel.add(serverNameField, c);
+
+ c.gridx = 0;
+ c.gridy++;
+ inputPanel.add(new JLabel("Address"), c);
+ serverAddressField = new JTextField(20);
+ c.gridx++;
+ inputPanel.add(serverAddressField, c);
+
+ c.gridy++;
+ c.gridx = 0;
+ inputPanel.add(new JLabel("Username"), c);
+ usernameField = new JTextField(20);
+ c.gridx++;
+ inputPanel.add(usernameField, c);
+
+ container.add(inputPanel, BorderLayout.CENTER);
+
+ JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
+ JButton cancelButton = new JButton("Cancel");
+ cancelButton.addActionListener(e -> {
+ this.serverInfo = null;
+ this.dispose();
+ });
+ JButton okButton = new JButton("Ok");
+ okButton.addActionListener(e -> {
+ var messages = this.validateInputs();
+ if (!messages.isEmpty()) {
+ String msg = String.join("\n", messages);
+ JOptionPane.showMessageDialog(this, "The information you entered is not valid:\n" + msg, "Invalid Server Data", JOptionPane.WARNING_MESSAGE);
+ } else {
+ String username = this.usernameField.getText();
+ if (username.isBlank()) {
+ username = null;
+ }
+ this.serverInfo = new ServerInfo(this.serverNameField.getText(), this.serverAddressField.getText(), username);
+ this.dispose();
+ }
+ });
+ buttonPanel.add(okButton);
+ buttonPanel.add(cancelButton);
+
+ container.add(buttonPanel, BorderLayout.SOUTH);
+
+ return container;
+ }
+
+ private List validateInputs() {
+ String name = this.serverNameField.getText();
+ String address = this.serverAddressField.getText();
+ String username = this.usernameField.getText();
+ List messages = new ArrayList<>();
+
+ if (name == null || name.isBlank()) {
+ messages.add("Server name cannot be blank.");
+ }
+ if (name != null && name.length() > 32) {
+ messages.add("Server name is too long.");
+ }
+ if (address == null || address.isBlank()) {
+ messages.add("Server address cannot be blank.");
+ }
+ if (address != null && !Launcher.addressPattern.matcher(address).matches()) {
+ messages.add("Server address is not properly formatted as HOST:PORT.");
+ }
+ if (username != null && !username.isBlank() && username.length() > 16) {
+ messages.add("Username is too long. Maximum of 16 characters.");
+ }
+ if (username != null && !Launcher.usernamePattern.matcher(username).matches()) {
+ messages.add("Username should contain only letters, numbers, underscores, and hyphens.");
+ }
+ return messages;
+ }
+}
diff --git a/client/src/main/java/nl/andrewlalis/aos_client/launcher/servers/EditServerDialog.java b/client/src/main/java/nl/andrewlalis/aos_client/launcher/servers/EditServerDialog.java
new file mode 100644
index 0000000..0a2766d
--- /dev/null
+++ b/client/src/main/java/nl/andrewlalis/aos_client/launcher/servers/EditServerDialog.java
@@ -0,0 +1,13 @@
+package nl.andrewlalis.aos_client.launcher.servers;
+
+import java.awt.*;
+
+public class EditServerDialog extends AddServerDialog {
+ public EditServerDialog(Frame owner, ServerInfo server) {
+ super(owner);
+ this.setTitle("Edit Server - " + server.getName());
+ this.serverNameField.setText(server.getName());
+ this.serverAddressField.setText(server.getHost());
+ this.usernameField.setText(server.getUsername());
+ }
+}
diff --git a/client/src/main/java/nl/andrewlalis/aos_client/launcher/servers/ServerInfo.java b/client/src/main/java/nl/andrewlalis/aos_client/launcher/servers/ServerInfo.java
index fdfbc8e..cb4fba7 100644
--- a/client/src/main/java/nl/andrewlalis/aos_client/launcher/servers/ServerInfo.java
+++ b/client/src/main/java/nl/andrewlalis/aos_client/launcher/servers/ServerInfo.java
@@ -1,8 +1,9 @@
package nl.andrewlalis.aos_client.launcher.servers;
+import java.io.Serializable;
import java.util.Objects;
-public class ServerInfo implements Comparable {
+public class ServerInfo implements Comparable, Serializable {
private String name;
private String host;
private String username;
diff --git a/client/src/main/java/nl/andrewlalis/aos_client/launcher/servers/ServerInfoListModel.java b/client/src/main/java/nl/andrewlalis/aos_client/launcher/servers/ServerInfoListModel.java
index 4bd4408..146d172 100644
--- a/client/src/main/java/nl/andrewlalis/aos_client/launcher/servers/ServerInfoListModel.java
+++ b/client/src/main/java/nl/andrewlalis/aos_client/launcher/servers/ServerInfoListModel.java
@@ -1,15 +1,39 @@
package nl.andrewlalis.aos_client.launcher.servers;
+import nl.andrewlalis.aos_client.launcher.Launcher;
+
import javax.swing.*;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
+/**
+ * Model which represents the list of servers. This model is backed by a file
+ * containing the serialized list, which is updated any time a server is added,
+ * edited, or removed.
+ */
public class ServerInfoListModel extends AbstractListModel {
+ public static final Path SERVERS_FILE = Launcher.DATA_DIR.resolve("servers.dat");
+
private final List servers;
+ @SuppressWarnings("unchecked")
public ServerInfoListModel() {
- this.servers = new ArrayList<>();
+ List list = null;
+ if (Files.exists(SERVERS_FILE)) {
+ try (ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(SERVERS_FILE))) {
+ list = (ArrayList) ois.readObject();
+ } catch (IOException | ClassNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
+ if (list == null) list = new ArrayList<>();
+ servers = list;
}
public void add(ServerInfo server) {
@@ -17,6 +41,7 @@ public class ServerInfoListModel extends AbstractListModel {
this.servers.add(server);
this.servers.sort(Comparator.naturalOrder());
this.fireContentsChanged(this, 0, this.getSize());
+ this.save();
}
public void remove(ServerInfo server) {
@@ -24,10 +49,12 @@ public class ServerInfoListModel extends AbstractListModel {
if (index == -1) return;
this.servers.remove(index);
this.fireIntervalRemoved(this, index, index);
+ this.save();
}
public void serverEdited() {
this.fireContentsChanged(this, 0, this.getSize());
+ this.save();
}
@Override
@@ -39,4 +66,15 @@ public class ServerInfoListModel extends AbstractListModel {
public ServerInfo getElementAt(int index) {
return this.servers.get(index);
}
+
+ private void save() {
+ try {
+ Files.createDirectories(SERVERS_FILE.getParent());
+ ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(SERVERS_FILE));
+ oos.writeObject(this.servers);
+ oos.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
}
diff --git a/core/pom.xml b/core/pom.xml
index efc9979..4d86160 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -5,7 +5,7 @@
ace-of-shades
nl.andrewlalis
- 3.1
+ 4.0
4.0.0
diff --git a/pom.xml b/pom.xml
index 4cd0375..187fae8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
nl.andrewlalis
ace-of-shades
pom
- 3.1
+ 4.0
server
client
diff --git a/server/pom.xml b/server/pom.xml
index d557a68..d7adf69 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -5,7 +5,7 @@
ace-of-shades
nl.andrewlalis
- 3.1
+ 4.0
4.0.0