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>
|
<artifactId>lwjgl-stb</artifactId>
|
||||||
<classifier>${lwjgl.natives}</classifier>
|
<classifier>${lwjgl.natives}</classifier>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
@ -220,7 +219,7 @@
|
||||||
<configuration>
|
<configuration>
|
||||||
<archive>
|
<archive>
|
||||||
<manifest>
|
<manifest>
|
||||||
<mainClass>nl.andrewl.aos2_client.Aos2Client</mainClass>
|
<mainClass>nl.andrewl.aos2_client.Client</mainClass>
|
||||||
</manifest>
|
</manifest>
|
||||||
</archive>
|
</archive>
|
||||||
<descriptorRefs>
|
<descriptorRefs>
|
||||||
|
|
|
@ -14,7 +14,7 @@ import java.util.Random;
|
||||||
import static org.lwjgl.glfw.GLFW.*;
|
import static org.lwjgl.glfw.GLFW.*;
|
||||||
import static org.lwjgl.opengl.GL46.*;
|
import static org.lwjgl.opengl.GL46.*;
|
||||||
|
|
||||||
public class Aos2Client {
|
public class Client {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
var windowInfo = initUI();
|
var windowInfo = initUI();
|
||||||
long windowHandle = windowInfo.windowHandle();
|
long windowHandle = windowInfo.windowHandle();
|
||||||
|
@ -76,7 +76,7 @@ public class Aos2Client {
|
||||||
|
|
||||||
var vidMode = glfwGetVideoMode(glfwGetPrimaryMonitor());
|
var vidMode = glfwGetVideoMode(glfwGetPrimaryMonitor());
|
||||||
if (vidMode == null) throw new IllegalStateException("Could not get information about the primary monitory.");
|
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.");
|
if (windowHandle == 0) throw new RuntimeException("Failed to create GLFW window.");
|
||||||
|
|
||||||
glfwSetKeyCallback(windowHandle, (window, key, scancode, action, mods) -> {
|
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>
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>jitpack.io</id>
|
||||||
|
<url>https://jitpack.io</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<!-- https://mvnrepository.com/artifact/org.joml/joml -->
|
<!-- https://mvnrepository.com/artifact/org.joml/joml -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
@ -23,6 +30,11 @@
|
||||||
<artifactId>joml</artifactId>
|
<artifactId>joml</artifactId>
|
||||||
<version>1.10.4</version>
|
<version>1.10.4</version>
|
||||||
</dependency>
|
</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 -->
|
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
|
||||||
<dependency>
|
<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