Added version script, and beginnings of network code.
This commit is contained in:
parent
141e89951a
commit
00c22525cb
|
@ -210,7 +210,6 @@
|
|||
<artifactId>lwjgl-stb</artifactId>
|
||||
<classifier>${lwjgl.natives}</classifier>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
@ -220,7 +219,7 @@
|
|||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>nl.andrewl.aos2_client.Aos2Client</mainClass>
|
||||
<mainClass>nl.andrewl.aos2_client.Client</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
<descriptorRefs>
|
||||
|
|
|
@ -14,7 +14,7 @@ import java.util.Random;
|
|||
import static org.lwjgl.glfw.GLFW.*;
|
||||
import static org.lwjgl.opengl.GL46.*;
|
||||
|
||||
public class Aos2Client {
|
||||
public class Client {
|
||||
public static void main(String[] args) {
|
||||
var windowInfo = initUI();
|
||||
long windowHandle = windowInfo.windowHandle();
|
||||
|
@ -76,7 +76,7 @@ public class Aos2Client {
|
|||
|
||||
var vidMode = glfwGetVideoMode(glfwGetPrimaryMonitor());
|
||||
if (vidMode == null) throw new IllegalStateException("Could not get information about the primary monitory.");
|
||||
long windowHandle = glfwCreateWindow(vidMode.width(), vidMode.height(), "Ace of Shades 2", 0, 0);
|
||||
long windowHandle = glfwCreateWindow(vidMode.width(), vidMode.height(), "Ace of Shades 2", glfwGetPrimaryMonitor(), 0);
|
||||
if (windowHandle == 0) throw new RuntimeException("Failed to create GLFW window.");
|
||||
|
||||
glfwSetKeyCallback(windowHandle, (window, key, scancode, action, mods) -> {
|
12
core/pom.xml
12
core/pom.xml
|
@ -16,6 +16,13 @@
|
|||
<maven.compiler.target>17</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<!-- https://mvnrepository.com/artifact/org.joml/joml -->
|
||||
<dependency>
|
||||
|
@ -23,6 +30,11 @@
|
|||
<artifactId>joml</artifactId>
|
||||
<version>1.10.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.andrewlalis</groupId>
|
||||
<artifactId>record-net</artifactId>
|
||||
<version>v1.2.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
|
||||
<dependency>
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
package nl.andrewl.aos_core;
|
||||
|
||||
import nl.andrewl.aos_core.net.PlayerConnectRequestMessage;
|
||||
import nl.andrewl.record_net.Message;
|
||||
import nl.andrewl.record_net.Serializer;
|
||||
import nl.andrewl.record_net.util.ExtendedDataInputStream;
|
||||
import nl.andrewl.record_net.util.ExtendedDataOutputStream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Common wrapper for message serialization. All methods in this class are
|
||||
* thread-safe and meant for general use with any input or output streams.
|
||||
*/
|
||||
public final class Net {
|
||||
private Net() {}
|
||||
|
||||
private static final Serializer serializer = new Serializer();
|
||||
static {
|
||||
serializer.registerType(1, PlayerConnectRequestMessage.class);
|
||||
}
|
||||
|
||||
public static ExtendedDataInputStream getInputStream(InputStream in) {
|
||||
return new ExtendedDataInputStream(serializer, in);
|
||||
}
|
||||
|
||||
public static ExtendedDataOutputStream getOutputStream(OutputStream out) {
|
||||
return new ExtendedDataOutputStream(serializer, out);
|
||||
}
|
||||
|
||||
public static void write(Message msg, ExtendedDataOutputStream out) throws IOException {
|
||||
serializer.writeMessage(msg, out);
|
||||
}
|
||||
|
||||
public static byte[] write(Message msg) throws IOException {
|
||||
return serializer.writeMessage(msg);
|
||||
}
|
||||
|
||||
public static Message read(ExtendedDataInputStream in) throws IOException {
|
||||
return serializer.readMessage(in);
|
||||
}
|
||||
|
||||
public static Message read(byte[] data) throws IOException {
|
||||
return serializer.readMessage(data);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package nl.andrewl.aos_core.net;
|
||||
|
||||
import nl.andrewl.record_net.Message;
|
||||
|
||||
public record PlayerConnectRejectMessage (String reason) implements Message {}
|
|
@ -0,0 +1,8 @@
|
|||
package nl.andrewl.aos_core.net;
|
||||
|
||||
import nl.andrewl.record_net.Message;
|
||||
|
||||
public record PlayerConnectRequestMessage (
|
||||
String username,
|
||||
int udpPort
|
||||
) implements Message {}
|
|
@ -0,0 +1,5 @@
|
|||
package nl.andrewl.aos_core.net.udp;
|
||||
|
||||
import nl.andrewl.record_net.Message;
|
||||
|
||||
public record InitPacket () implements Message {}
|
|
@ -0,0 +1,12 @@
|
|||
# AOS-2 Network Protocol
|
||||
This document describes the network protocol used by Ace of Shades 2 for server-client communication.
|
||||
|
||||
All communications, whether they be UDP or TCP, use the [record-net](https://github.com/andrewlalis/record-net) library for sending packets as serialized records.
|
||||
|
||||
When referring to the names of packets, we will assume a common package name of `nl.andrewl.aos_core.net`.
|
||||
|
||||
### Player Connection
|
||||
This workflow is involved in the establishment of a connection between the client and server.
|
||||
|
||||
1. Player sends a `PlayerConnectRequestMessage` via TCP, immediately upon opening a socket connection. It contains the player's desired `username`, and their `udpPort` that they will use to connect.
|
||||
2. The server will respond with either a `PlayerConnectRejectMessage` with a `reason` for the rejection, or a `PlayerConnectAcceptMessage`.
|
|
@ -0,0 +1,112 @@
|
|||
package nl.andrewl.aos2_server;
|
||||
|
||||
import nl.andrewl.aos_core.Net;
|
||||
import nl.andrewl.aos_core.net.PlayerConnectRejectMessage;
|
||||
import nl.andrewl.aos_core.net.PlayerConnectRequestMessage;
|
||||
import nl.andrewl.record_net.Message;
|
||||
import nl.andrewl.record_net.util.ExtendedDataInputStream;
|
||||
import nl.andrewl.record_net.util.ExtendedDataOutputStream;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
|
||||
public class ClientHandler extends Thread {
|
||||
private static int nextThreadId = 1;
|
||||
|
||||
private final Server server;
|
||||
private final Socket socket;
|
||||
private final DatagramSocket datagramSocket;
|
||||
private final ExtendedDataInputStream in;
|
||||
private final ExtendedDataOutputStream out;
|
||||
|
||||
private volatile boolean running;
|
||||
private InetAddress clientAddress;
|
||||
private int clientUdpPort;
|
||||
|
||||
public ClientHandler(Server server, Socket socket, DatagramSocket datagramSocket) throws IOException {
|
||||
super("aos-client-handler-" + nextThreadId++);
|
||||
this.server = server;
|
||||
this.socket = socket;
|
||||
this.datagramSocket = datagramSocket;
|
||||
this.in = Net.getInputStream(socket.getInputStream());
|
||||
this.out = Net.getOutputStream(socket.getOutputStream());
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
running = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
running = true;
|
||||
establishConnection();
|
||||
while (running) {
|
||||
try {
|
||||
Message msg = Net.read(in);
|
||||
} catch (SocketException e) {
|
||||
if (e.getMessage().equals("Socket closed") | e.getMessage().equals("Connection reset")) {
|
||||
shutdown();
|
||||
} else {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} catch (EOFException e) {
|
||||
shutdown();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void establishConnection() {
|
||||
boolean connectionEstablished = false;
|
||||
int attempts = 0;
|
||||
while (!connectionEstablished && attempts < 100) {
|
||||
try {
|
||||
Message msg = Net.read(in);
|
||||
if (msg instanceof PlayerConnectRequestMessage connectMsg) {
|
||||
this.clientAddress = socket.getInetAddress();
|
||||
this.clientUdpPort = connectMsg.udpPort();
|
||||
System.out.println("Player connected: " + connectMsg.username());
|
||||
connectionEstablished = true;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
attempts++;
|
||||
}
|
||||
if (!connectionEstablished) {
|
||||
try {
|
||||
Net.write(new PlayerConnectRejectMessage("Too many connect attempts failed."), out);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
System.out.println("Player couldn't connect after " + attempts + " attempts. Aborting.");
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
private void sendDatagramPacket(Message msg) {
|
||||
try {
|
||||
sendDatagramPacket(Net.write(msg));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void sendDatagramPacket(byte[] data) {
|
||||
DatagramPacket packet = new DatagramPacket(data, data.length, clientAddress, clientUdpPort);
|
||||
sendDatagramPacket(packet);
|
||||
}
|
||||
|
||||
private void sendDatagramPacket(DatagramPacket packet) {
|
||||
try {
|
||||
packet.setAddress(clientAddress);
|
||||
packet.setPort(clientUdpPort);
|
||||
datagramSocket.send(packet);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package nl.andrewl.aos2_server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class Server implements Runnable {
|
||||
private final ServerSocket serverSocket;
|
||||
private final DatagramSocket datagramSocket;
|
||||
private volatile boolean running;
|
||||
private Set<ClientHandler> clientHandlers;
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
new Server().run();
|
||||
}
|
||||
|
||||
public Server() throws IOException {
|
||||
this.serverSocket = new ServerSocket(24464, 5);
|
||||
this.serverSocket.setReuseAddress(true);
|
||||
this.datagramSocket = new DatagramSocket(24464);
|
||||
this.datagramSocket.setReuseAddress(true);
|
||||
this.clientHandlers = new HashSet<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
running = true;
|
||||
System.out.println("Started AOS2-Server on TCP/UDP port " + serverSocket.getLocalPort() + "; now accepting connections.");
|
||||
while (running) {
|
||||
acceptClientConnection();
|
||||
}
|
||||
}
|
||||
|
||||
private void acceptClientConnection() {
|
||||
try {
|
||||
Socket clientSocket = serverSocket.accept();
|
||||
ClientHandler handler = new ClientHandler(this, clientSocket, datagramSocket);
|
||||
handler.start();
|
||||
clientHandlers.add(handler);
|
||||
} catch (IOException e) {
|
||||
if (e instanceof SocketException && !this.running && e.getMessage().equalsIgnoreCase("Socket closed")) {
|
||||
return; // Ignore this exception, since it is expected on shutdown.
|
||||
}
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
#!/usr/bin/rdmd
|
||||
/**
|
||||
* This module takes the main parent POM's version, and applies it to all child
|
||||
* modules.
|
||||
*
|
||||
* While you can run this with `./setversion.d`, it's faster if you compile
|
||||
* with `dmd setversion.d` and then just run `./setversion`.
|
||||
*/
|
||||
module setversion;
|
||||
|
||||
import std.stdio;
|
||||
import std.file : write, readText;
|
||||
|
||||
void main() {
|
||||
string newVersion = getMainVersion();
|
||||
writefln!"Setting all modules to version %s"(newVersion);
|
||||
string[] files = ["client/pom.xml", "core/pom.xml", "server/pom.xml"];
|
||||
foreach (pomFile; files) {
|
||||
string xml = replaceVersion(readText(pomFile), newVersion);
|
||||
write(pomFile, xml);
|
||||
writefln!"Updated %s to version %s"(pomFile, newVersion);
|
||||
}
|
||||
}
|
||||
|
||||
string getMainVersion() {
|
||||
import std.file : readText;
|
||||
import std.regex;
|
||||
auto versionRegex = ctRegex!(`<version>(\S+)<\/version>`);
|
||||
auto c = matchFirst(readText("pom.xml"), versionRegex);
|
||||
return c[1];
|
||||
}
|
||||
|
||||
string replaceVersion(string xml, string newVersion) {
|
||||
import std.regex;
|
||||
import std.string : strip, indexOf;
|
||||
auto versionRegex = ctRegex!(`<parent>[\s\S]*<version>(\S+)<\/version>[\s\S]*<\/parent>`);
|
||||
auto c = matchFirst(xml, versionRegex);
|
||||
if (!c.empty) {
|
||||
string currentVersion = c[1];
|
||||
auto hitIndex = c.hit.indexOf(currentVersion);
|
||||
string prefix = xml[0 .. c.pre.length + hitIndex];
|
||||
string suffix = xml[c.pre.length + hitIndex + currentVersion.length .. $];
|
||||
return prefix ~ newVersion ~ suffix;
|
||||
}
|
||||
return xml;
|
||||
}
|
Loading…
Reference in New Issue