Added server-controlled physics base.
This commit is contained in:
parent
e66c94680d
commit
f2b0e09979
|
@ -1,6 +1,7 @@
|
||||||
package nl.andrewl.aos2_client;
|
package nl.andrewl.aos2_client;
|
||||||
|
|
||||||
import nl.andrewl.aos_core.MathUtils;
|
import nl.andrewl.aos_core.MathUtils;
|
||||||
|
import nl.andrewl.aos_core.net.udp.ClientOrientationState;
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
import org.joml.Vector2f;
|
import org.joml.Vector2f;
|
||||||
import org.joml.Vector3f;
|
import org.joml.Vector3f;
|
||||||
|
@ -19,6 +20,8 @@ public class Camera implements GLFWCursorPosCallbackI {
|
||||||
public static final Vector3f FORWARD = new Vector3f(0, 0, -1);
|
public static final Vector3f FORWARD = new Vector3f(0, 0, -1);
|
||||||
public static final Vector3f BACKWARD = new Vector3f(0, 0, 1);
|
public static final Vector3f BACKWARD = new Vector3f(0, 0, 1);
|
||||||
|
|
||||||
|
private final Client client;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The x, y, and z position of the camera in the world.
|
* The x, y, and z position of the camera in the world.
|
||||||
*/
|
*/
|
||||||
|
@ -44,7 +47,8 @@ public class Camera implements GLFWCursorPosCallbackI {
|
||||||
private float lastMouseCursorY;
|
private float lastMouseCursorY;
|
||||||
private float mouseCursorSensitivity = 0.005f;
|
private float mouseCursorSensitivity = 0.005f;
|
||||||
|
|
||||||
public Camera() {
|
public Camera(Client client) {
|
||||||
|
this.client = client;
|
||||||
this.position = new Vector3f();
|
this.position = new Vector3f();
|
||||||
this.orientation = new Vector2f(0, (float) (Math.PI / 2));
|
this.orientation = new Vector2f(0, (float) (Math.PI / 2));
|
||||||
this.viewTransform = new Matrix4f();
|
this.viewTransform = new Matrix4f();
|
||||||
|
@ -63,9 +67,11 @@ public class Camera implements GLFWCursorPosCallbackI {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPosition(float x, float y, float z) {
|
public void setPosition(float x, float y, float z) {
|
||||||
position.set(x, y, z);
|
if (position.x != x || position.y != y || position.z != z) {
|
||||||
updateViewTransform();
|
position.set(x, y, z);
|
||||||
System.out.printf("Position: x=%.2f, y=%.2f, z=%.2f%n", position.x, position.y, position.z);
|
updateViewTransform();
|
||||||
|
System.out.printf("Position: x=%.2f, y=%.2f, z=%.2f%n", position.x, position.y, position.z);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOrientation(float x, float y) {
|
public void setOrientation(float x, float y) {
|
||||||
|
@ -100,17 +106,19 @@ public class Camera implements GLFWCursorPosCallbackI {
|
||||||
lastMouseCursorX = x;
|
lastMouseCursorX = x;
|
||||||
lastMouseCursorY = y;
|
lastMouseCursorY = y;
|
||||||
setOrientation(orientation.x - dx * mouseCursorSensitivity, orientation.y - dy * mouseCursorSensitivity);
|
setOrientation(orientation.x - dx * mouseCursorSensitivity, orientation.y - dy * mouseCursorSensitivity);
|
||||||
|
client.getCommunicationHandler().sendDatagramPacket(new ClientOrientationState(client.getClientId(), orientation.x, orientation.y));
|
||||||
// System.out.printf("rX=%.0f deg about the Y axis, rY=%.0f deg about the X axis%n", Math.toDegrees(orientation.x), Math.toDegrees(orientation.y));
|
// System.out.printf("rX=%.0f deg about the Y axis, rY=%.0f deg about the X axis%n", Math.toDegrees(orientation.x), Math.toDegrees(orientation.y));
|
||||||
var vv = getViewVector();
|
var vv = getViewVector();
|
||||||
// System.out.printf("View vector: [%.2f, %.2f, %.2f]%n", vv.x, vv.y, vv.z);
|
// System.out.printf("View vector: [%.2f, %.2f, %.2f]%n", vv.x, vv.y, vv.z);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vector3f getViewVector() {
|
public Vector3f getViewVector() {
|
||||||
|
float y = (float) (orientation.y + Math.PI / 2);
|
||||||
return new Vector3f(
|
return new Vector3f(
|
||||||
(float) -Math.sin(orientation.x),
|
(float) (Math.sin(orientation.x) * Math.cos(y)),
|
||||||
(float) -Math.cos(orientation.y),
|
(float) -Math.sin(y),
|
||||||
(float) Math.cos(orientation.x)
|
(float) (Math.cos(orientation.x) * Math.cos(y))
|
||||||
);
|
).normalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void move(Vector3f relativeMotion) {
|
public void move(Vector3f relativeMotion) {
|
||||||
|
@ -120,6 +128,6 @@ public class Camera implements GLFWCursorPosCallbackI {
|
||||||
moveTransform.transformDirection(actualMotion);
|
moveTransform.transformDirection(actualMotion);
|
||||||
position.add(actualMotion);
|
position.add(actualMotion);
|
||||||
updateViewTransform();
|
updateViewTransform();
|
||||||
System.out.printf("Position: x=%.2f, y=%.2f, z=%.2f%n", position.x, position.y, position.z);
|
// System.out.printf("Position: x=%.2f, y=%.2f, z=%.2f%n", position.x, position.y, position.z);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import nl.andrewl.aos2_client.render.ChunkMesh;
|
||||||
import nl.andrewl.aos2_client.render.ChunkRenderer;
|
import nl.andrewl.aos2_client.render.ChunkRenderer;
|
||||||
import nl.andrewl.aos2_client.render.WindowUtils;
|
import nl.andrewl.aos2_client.render.WindowUtils;
|
||||||
import nl.andrewl.aos_core.model.World;
|
import nl.andrewl.aos_core.model.World;
|
||||||
|
import nl.andrewl.aos_core.net.udp.ClientInputState;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
|
@ -26,8 +27,10 @@ public class Client implements Runnable {
|
||||||
private String username;
|
private String username;
|
||||||
private CommunicationHandler communicationHandler;
|
private CommunicationHandler communicationHandler;
|
||||||
private ChunkRenderer chunkRenderer;
|
private ChunkRenderer chunkRenderer;
|
||||||
|
private int clientId;
|
||||||
|
|
||||||
private World world;
|
private World world;
|
||||||
|
private Camera cam;
|
||||||
|
|
||||||
public Client(InetAddress serverAddress, int serverPort, String username) {
|
public Client(InetAddress serverAddress, int serverPort, String username) {
|
||||||
this.serverAddress = serverAddress;
|
this.serverAddress = serverAddress;
|
||||||
|
@ -35,6 +38,7 @@ public class Client implements Runnable {
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.communicationHandler = new CommunicationHandler(this);
|
this.communicationHandler = new CommunicationHandler(this);
|
||||||
this.world = new World();
|
this.world = new World();
|
||||||
|
this.cam = new Camera(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -44,7 +48,7 @@ public class Client implements Runnable {
|
||||||
chunkRenderer = new ChunkRenderer(windowInfo.width(), windowInfo.height());
|
chunkRenderer = new ChunkRenderer(windowInfo.width(), windowInfo.height());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
communicationHandler.establishConnection(serverAddress, serverPort, username);
|
this.clientId = communicationHandler.establishConnection(serverAddress, serverPort, username);
|
||||||
System.out.println("Established connection to the server.");
|
System.out.println("Established connection to the server.");
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
@ -61,11 +65,9 @@ public class Client implements Runnable {
|
||||||
chunkRenderer.addChunkMesh(new ChunkMesh(chunk));
|
chunkRenderer.addChunkMesh(new ChunkMesh(chunk));
|
||||||
}
|
}
|
||||||
|
|
||||||
Camera cam = new Camera();
|
|
||||||
cam.setOrientationDegrees(90, 90);
|
|
||||||
cam.setPosition(0, 48, 0);
|
|
||||||
glfwSetCursorPosCallback(windowHandle, cam);
|
glfwSetCursorPosCallback(windowHandle, cam);
|
||||||
|
|
||||||
|
ClientInputState lastInputState = null;
|
||||||
|
|
||||||
while (!glfwWindowShouldClose(windowHandle)) {
|
while (!glfwWindowShouldClose(windowHandle)) {
|
||||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
|
@ -75,12 +77,20 @@ public class Client implements Runnable {
|
||||||
glfwSwapBuffers(windowHandle);
|
glfwSwapBuffers(windowHandle);
|
||||||
glfwPollEvents();
|
glfwPollEvents();
|
||||||
|
|
||||||
if (glfwGetKey(windowHandle, GLFW_KEY_W) == GLFW_PRESS) cam.move(Camera.FORWARD);
|
ClientInputState inputState = new ClientInputState(
|
||||||
if (glfwGetKey(windowHandle, GLFW_KEY_S) == GLFW_PRESS) cam.move(Camera.BACKWARD);
|
clientId,
|
||||||
if (glfwGetKey(windowHandle, GLFW_KEY_A) == GLFW_PRESS) cam.move(Camera.LEFT);
|
glfwGetKey(windowHandle, GLFW_KEY_W) == GLFW_PRESS,
|
||||||
if (glfwGetKey(windowHandle, GLFW_KEY_D) == GLFW_PRESS) cam.move(Camera.RIGHT);
|
glfwGetKey(windowHandle, GLFW_KEY_S) == GLFW_PRESS,
|
||||||
if (glfwGetKey(windowHandle, GLFW_KEY_SPACE) == GLFW_PRESS) cam.move(Camera.UP);
|
glfwGetKey(windowHandle, GLFW_KEY_A) == GLFW_PRESS,
|
||||||
if (glfwGetKey(windowHandle, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS) cam.move(Camera.DOWN);
|
glfwGetKey(windowHandle, GLFW_KEY_D) == GLFW_PRESS,
|
||||||
|
glfwGetKey(windowHandle, GLFW_KEY_SPACE) == GLFW_PRESS,
|
||||||
|
glfwGetKey(windowHandle, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
if (!inputState.equals(lastInputState)) {
|
||||||
|
communicationHandler.sendDatagramPacket(inputState);
|
||||||
|
lastInputState = inputState;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
communicationHandler.shutdown();
|
communicationHandler.shutdown();
|
||||||
|
@ -89,10 +99,22 @@ public class Client implements Runnable {
|
||||||
WindowUtils.clearUI(windowHandle);
|
WindowUtils.clearUI(windowHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getClientId() {
|
||||||
|
return clientId;
|
||||||
|
}
|
||||||
|
|
||||||
public World getWorld() {
|
public World getWorld() {
|
||||||
return world;
|
return world;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Camera getCam() {
|
||||||
|
return cam;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommunicationHandler getCommunicationHandler() {
|
||||||
|
return communicationHandler;
|
||||||
|
}
|
||||||
|
|
||||||
public ChunkRenderer getChunkRenderer() {
|
public ChunkRenderer getChunkRenderer() {
|
||||||
return chunkRenderer;
|
return chunkRenderer;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,12 @@ import nl.andrewl.aos_core.Net;
|
||||||
import nl.andrewl.aos_core.model.Chunk;
|
import nl.andrewl.aos_core.model.Chunk;
|
||||||
import nl.andrewl.aos_core.net.*;
|
import nl.andrewl.aos_core.net.*;
|
||||||
import nl.andrewl.aos_core.net.udp.DatagramInit;
|
import nl.andrewl.aos_core.net.udp.DatagramInit;
|
||||||
|
import nl.andrewl.aos_core.net.udp.PlayerUpdateMessage;
|
||||||
import nl.andrewl.record_net.Message;
|
import nl.andrewl.record_net.Message;
|
||||||
import nl.andrewl.record_net.util.ExtendedDataInputStream;
|
import nl.andrewl.record_net.util.ExtendedDataInputStream;
|
||||||
import nl.andrewl.record_net.util.ExtendedDataOutputStream;
|
import nl.andrewl.record_net.util.ExtendedDataOutputStream;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.DatagramPacket;
|
import java.net.DatagramPacket;
|
||||||
|
@ -20,6 +23,8 @@ import java.net.Socket;
|
||||||
* methods for sending messages and processing those we receive.
|
* methods for sending messages and processing those we receive.
|
||||||
*/
|
*/
|
||||||
public class CommunicationHandler {
|
public class CommunicationHandler {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(CommunicationHandler.class);
|
||||||
|
|
||||||
private final Client client;
|
private final Client client;
|
||||||
private Socket socket;
|
private Socket socket;
|
||||||
private DatagramSocket datagramSocket;
|
private DatagramSocket datagramSocket;
|
||||||
|
@ -31,7 +36,7 @@ public class CommunicationHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
public int establishConnection(InetAddress address, int port, String username) throws IOException {
|
public int establishConnection(InetAddress address, int port, String username) throws IOException {
|
||||||
System.out.printf("Connecting to server at %s, port %d, with username \"%s\"...%n", address, port, username);
|
log.debug("Connecting to server at {}, port {}, with username \"{}\"...", address, port, username);
|
||||||
if (socket != null && !socket.isClosed()) {
|
if (socket != null && !socket.isClosed()) {
|
||||||
socket.close();
|
socket.close();
|
||||||
}
|
}
|
||||||
|
@ -109,11 +114,10 @@ public class CommunicationHandler {
|
||||||
if (!connectionEstablished) {
|
if (!connectionEstablished) {
|
||||||
throw new IOException("Could not establish a datagram connection to the server after " + attempts + " attempts.");
|
throw new IOException("Could not establish a datagram connection to the server after " + attempts + " attempts.");
|
||||||
}
|
}
|
||||||
System.out.println("Established datagram communication with the server.");
|
log.debug("Established datagram communication with the server.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleMessage(Message msg) {
|
private void handleMessage(Message msg) {
|
||||||
System.out.println("Received message: " + msg);
|
|
||||||
if (msg instanceof ChunkDataMessage chunkDataMessage) {
|
if (msg instanceof ChunkDataMessage chunkDataMessage) {
|
||||||
Chunk chunk = chunkDataMessage.toChunk();
|
Chunk chunk = chunkDataMessage.toChunk();
|
||||||
client.getWorld().addChunk(chunk);
|
client.getWorld().addChunk(chunk);
|
||||||
|
@ -121,6 +125,11 @@ public class CommunicationHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleUdpMessage(Message msg, DatagramPacket packet) {
|
private void handleUdpMessage(Message msg, DatagramPacket packet) {
|
||||||
System.out.println("Received udp message: " + msg);
|
if (msg instanceof PlayerUpdateMessage playerUpdate) {
|
||||||
|
// log.debug("Received player update: {}", playerUpdate);
|
||||||
|
if (playerUpdate.clientId() == client.getClientId()) {
|
||||||
|
client.getCam().setPosition(playerUpdate.px(), playerUpdate.py() + 1.8f, playerUpdate.pz());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@ public class ChunkRenderer {
|
||||||
private final ShaderProgram shaderProgram;
|
private final ShaderProgram shaderProgram;
|
||||||
private final int projectionTransformUniform;
|
private final int projectionTransformUniform;
|
||||||
private final int viewTransformUniform;
|
private final int viewTransformUniform;
|
||||||
private final int normalTransformUniform;
|
|
||||||
private final int chunkPositionUniform;
|
private final int chunkPositionUniform;
|
||||||
private final int chunkSizeUniform;
|
private final int chunkSizeUniform;
|
||||||
|
|
||||||
|
@ -35,7 +34,6 @@ public class ChunkRenderer {
|
||||||
shaderProgram.use();
|
shaderProgram.use();
|
||||||
this.projectionTransformUniform = shaderProgram.getUniform("projectionTransform");
|
this.projectionTransformUniform = shaderProgram.getUniform("projectionTransform");
|
||||||
this.viewTransformUniform = shaderProgram.getUniform("viewTransform");
|
this.viewTransformUniform = shaderProgram.getUniform("viewTransform");
|
||||||
this.normalTransformUniform = shaderProgram.getUniform("normalTransform");
|
|
||||||
this.chunkPositionUniform = shaderProgram.getUniform("chunkPosition");
|
this.chunkPositionUniform = shaderProgram.getUniform("chunkPosition");
|
||||||
this.chunkSizeUniform = shaderProgram.getUniform("chunkSize");
|
this.chunkSizeUniform = shaderProgram.getUniform("chunkSize");
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,10 @@ public class WindowUtils {
|
||||||
|
|
||||||
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", glfwGetPrimaryMonitor(), 0);
|
int width = vidMode.width();
|
||||||
|
int height = vidMode.height();
|
||||||
|
width = 800; height = 600;
|
||||||
|
long windowHandle = glfwCreateWindow(width, height, "Ace of Shades 2", 0, 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) -> {
|
||||||
|
@ -43,7 +46,7 @@ public class WindowUtils {
|
||||||
glEnable(GL_DEPTH_TEST);
|
glEnable(GL_DEPTH_TEST);
|
||||||
glCullFace(GL_BACK);
|
glCullFace(GL_BACK);
|
||||||
|
|
||||||
return new WindowInfo(windowHandle, vidMode.width(), vidMode.height());
|
return new WindowInfo(windowHandle, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void clearUI(long windowHandle) {
|
public static void clearUI(long windowHandle) {
|
||||||
|
|
15
core/pom.xml
15
core/pom.xml
|
@ -33,7 +33,7 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.andrewlalis</groupId>
|
<groupId>com.github.andrewlalis</groupId>
|
||||||
<artifactId>record-net</artifactId>
|
<artifactId>record-net</artifactId>
|
||||||
<version>v1.2.1</version>
|
<version>v1.3.4</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- https://github.com/OpenHFT/Zero-Allocation-Hashing -->
|
<!-- https://github.com/OpenHFT/Zero-Allocation-Hashing -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
@ -41,6 +41,19 @@
|
||||||
<artifactId>zero-allocation-hashing</artifactId>
|
<artifactId>zero-allocation-hashing</artifactId>
|
||||||
<version>0.15</version>
|
<version>0.15</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
<version>1.7.36</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j-impl -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
|
<artifactId>log4j-slf4j-impl</artifactId>
|
||||||
|
<version>2.18.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
|
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package nl.andrewl.aos_core;
|
package nl.andrewl.aos_core;
|
||||||
|
|
||||||
import nl.andrewl.aos_core.net.*;
|
import nl.andrewl.aos_core.net.*;
|
||||||
import nl.andrewl.aos_core.net.udp.DatagramInit;
|
import nl.andrewl.aos_core.net.udp.*;
|
||||||
import nl.andrewl.record_net.Message;
|
import nl.andrewl.record_net.Message;
|
||||||
import nl.andrewl.record_net.Serializer;
|
import nl.andrewl.record_net.Serializer;
|
||||||
import nl.andrewl.record_net.util.ExtendedDataInputStream;
|
import nl.andrewl.record_net.util.ExtendedDataInputStream;
|
||||||
|
@ -26,6 +26,10 @@ public final class Net {
|
||||||
serializer.registerType(4, DatagramInit.class);
|
serializer.registerType(4, DatagramInit.class);
|
||||||
serializer.registerType(5, ChunkHashMessage.class);
|
serializer.registerType(5, ChunkHashMessage.class);
|
||||||
serializer.registerType(6, ChunkDataMessage.class);
|
serializer.registerType(6, ChunkDataMessage.class);
|
||||||
|
serializer.registerType(7, ChunkUpdateMessage.class);
|
||||||
|
serializer.registerType(8, ClientInputState.class);
|
||||||
|
serializer.registerType(9, ClientOrientationState.class);
|
||||||
|
serializer.registerType(10, PlayerUpdateMessage.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ExtendedDataInputStream getInputStream(InputStream in) {
|
public static ExtendedDataInputStream getInputStream(InputStream in) {
|
||||||
|
|
|
@ -1,12 +1,47 @@
|
||||||
package nl.andrewl.aos_core.model;
|
package nl.andrewl.aos_core.model;
|
||||||
|
|
||||||
|
import nl.andrewl.aos_core.MathUtils;
|
||||||
import org.joml.Vector2f;
|
import org.joml.Vector2f;
|
||||||
import org.joml.Vector3f;
|
import org.joml.Vector3f;
|
||||||
|
|
||||||
|
import static org.joml.Math.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic information about a player that both the client and server should
|
||||||
|
* know.
|
||||||
|
*/
|
||||||
public class Player {
|
public class Player {
|
||||||
|
/**
|
||||||
|
* The player's position. This is the position of their feet. So if a
|
||||||
|
* player is standing on a block at y=5 (block occupies space from 4 to 5)
|
||||||
|
* then the player's y coordinate is y=6.0. The x and z coordinates are
|
||||||
|
* simply the center of the player.
|
||||||
|
*/
|
||||||
private final Vector3f position;
|
private final Vector3f position;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The player's velocity in each of the coordinate axes.
|
||||||
|
*/
|
||||||
private final Vector3f velocity;
|
private final Vector3f velocity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The player's orientation. The x component refers to rotation about the
|
||||||
|
* vertical axis, and the y component refers to rotation about the
|
||||||
|
* horizontal axis. The x component is limited to between 0 and 2 PI, where
|
||||||
|
* x=0 means the player is looking towards the +Z axis. x increases in a
|
||||||
|
* counterclockwise fashion.
|
||||||
|
* The y component is limited to between 0 and PI, with y=0 looking
|
||||||
|
* straight down, and y=PI looking straight up.
|
||||||
|
*/
|
||||||
private final Vector2f orientation;
|
private final Vector2f orientation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A vector that's internally re-computed each time the player's
|
||||||
|
* orientation changes, and represents unit vector pointing in the
|
||||||
|
* direction the player is looking.
|
||||||
|
*/
|
||||||
|
private final Vector3f viewVector;
|
||||||
|
|
||||||
private final String username;
|
private final String username;
|
||||||
private final int id;
|
private final int id;
|
||||||
|
|
||||||
|
@ -14,6 +49,7 @@ public class Player {
|
||||||
this.position = new Vector3f();
|
this.position = new Vector3f();
|
||||||
this.velocity = new Vector3f();
|
this.velocity = new Vector3f();
|
||||||
this.orientation = new Vector2f();
|
this.orientation = new Vector2f();
|
||||||
|
this.viewVector = new Vector3f();
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.username = username;
|
this.username = username;
|
||||||
}
|
}
|
||||||
|
@ -22,14 +58,28 @@ public class Player {
|
||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setPosition(Vector3f position) {
|
||||||
|
this.position.set(position);
|
||||||
|
}
|
||||||
|
|
||||||
public Vector3f getVelocity() {
|
public Vector3f getVelocity() {
|
||||||
return velocity;
|
return velocity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setVelocity(Vector3f velocity) {
|
||||||
|
this.velocity.set(velocity);
|
||||||
|
}
|
||||||
|
|
||||||
public Vector2f getOrientation() {
|
public Vector2f getOrientation() {
|
||||||
return orientation;
|
return orientation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setOrientation(float x, float y) {
|
||||||
|
orientation.set(MathUtils.normalize(x, 0, PI * 2), MathUtils.clamp(y, 0, (float) PI));
|
||||||
|
y = orientation.y + (float) PI / 2f;
|
||||||
|
viewVector.set(sin(orientation.x) * cos(y), -sin(y), cos(orientation.x) * cos(y)).normalize();
|
||||||
|
}
|
||||||
|
|
||||||
public String getUsername() {
|
public String getUsername() {
|
||||||
return username;
|
return username;
|
||||||
}
|
}
|
||||||
|
@ -37,4 +87,8 @@ public class Player {
|
||||||
public int getId() {
|
public int getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Vector3f getViewVector() {
|
||||||
|
return viewVector;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package nl.andrewl.aos_core.net.udp;
|
||||||
|
|
||||||
|
import nl.andrewl.record_net.Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message that' sent periodically by the client when the player's input
|
||||||
|
* changes.
|
||||||
|
*/
|
||||||
|
public record ClientInputState(
|
||||||
|
int clientId,
|
||||||
|
boolean forward,
|
||||||
|
boolean backward,
|
||||||
|
boolean left,
|
||||||
|
boolean right,
|
||||||
|
boolean jumping,
|
||||||
|
boolean crouching,
|
||||||
|
boolean sprinting
|
||||||
|
) implements Message {}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package nl.andrewl.aos_core.net.udp;
|
||||||
|
|
||||||
|
import nl.andrewl.record_net.Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message sent by clients when they update their player's orientation.
|
||||||
|
* @param clientId The client's id.
|
||||||
|
* @param x The rotation about the vertical axis.
|
||||||
|
* @param y The rotation about the horizontal axis.
|
||||||
|
*/
|
||||||
|
public record ClientOrientationState(
|
||||||
|
int clientId,
|
||||||
|
float x, float y
|
||||||
|
) implements Message {}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package nl.andrewl.aos_core.net.udp;
|
||||||
|
|
||||||
|
import nl.andrewl.aos_core.model.Player;
|
||||||
|
import nl.andrewl.record_net.Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This message is sent by the server to clients whenever a player has updated
|
||||||
|
* in some way, like movement or orientation or held items.
|
||||||
|
*/
|
||||||
|
public record PlayerUpdateMessage(
|
||||||
|
int clientId,
|
||||||
|
float px, float py, float pz,
|
||||||
|
float vx, float vy, float vz,
|
||||||
|
float ox, float oy
|
||||||
|
) implements Message {
|
||||||
|
public PlayerUpdateMessage(Player player) {
|
||||||
|
this(
|
||||||
|
player.getId(),
|
||||||
|
player.getPosition().x, player.getPosition().y, player.getPosition().z,
|
||||||
|
player.getVelocity().x, player.getVelocity().y, player.getVelocity().z,
|
||||||
|
player.getOrientation().x, player.getOrientation().y
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
appenders = console
|
||||||
|
appender.console.type = Console
|
||||||
|
appender.console.name = STDOUT
|
||||||
|
appender.console.layout.type = PatternLayout
|
||||||
|
appender.console.layout.pattern = [%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n
|
||||||
|
|
||||||
|
rootLogger.level = debug
|
||||||
|
rootLogger.appenderRefs = stdout
|
||||||
|
rootLogger.appenderRef.stdout.ref = STDOUT
|
|
@ -1,11 +1,15 @@
|
||||||
package nl.andrewl.aos2_server;
|
package nl.andrewl.aos2_server;
|
||||||
|
|
||||||
import nl.andrewl.aos_core.Net;
|
import nl.andrewl.aos_core.Net;
|
||||||
import nl.andrewl.aos_core.model.Player;
|
import nl.andrewl.aos_core.model.Chunk;
|
||||||
import nl.andrewl.aos_core.net.*;
|
import nl.andrewl.aos_core.net.*;
|
||||||
|
import nl.andrewl.aos_core.net.udp.PlayerUpdateMessage;
|
||||||
import nl.andrewl.record_net.Message;
|
import nl.andrewl.record_net.Message;
|
||||||
import nl.andrewl.record_net.util.ExtendedDataInputStream;
|
import nl.andrewl.record_net.util.ExtendedDataInputStream;
|
||||||
import nl.andrewl.record_net.util.ExtendedDataOutputStream;
|
import nl.andrewl.record_net.util.ExtendedDataOutputStream;
|
||||||
|
import org.joml.Vector3i;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.DatagramPacket;
|
import java.net.DatagramPacket;
|
||||||
|
@ -22,6 +26,8 @@ import java.net.Socket;
|
||||||
* from them.
|
* from them.
|
||||||
*/
|
*/
|
||||||
public class ClientCommunicationHandler {
|
public class ClientCommunicationHandler {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(ClientCommunicationHandler.class);
|
||||||
|
|
||||||
private final Server server;
|
private final Server server;
|
||||||
private final Socket socket;
|
private final Socket socket;
|
||||||
private final DatagramSocket datagramSocket;
|
private final DatagramSocket datagramSocket;
|
||||||
|
@ -29,8 +35,8 @@ public class ClientCommunicationHandler {
|
||||||
private final ExtendedDataOutputStream out;
|
private final ExtendedDataOutputStream out;
|
||||||
|
|
||||||
private InetAddress clientAddress;
|
private InetAddress clientAddress;
|
||||||
private int clientUdpPort;
|
private int clientUdpPort = -1;
|
||||||
private Player player;
|
private ServerPlayer player;
|
||||||
|
|
||||||
public ClientCommunicationHandler(Server server, Socket socket, DatagramSocket datagramSocket) throws IOException {
|
public ClientCommunicationHandler(Server server, Socket socket, DatagramSocket datagramSocket) throws IOException {
|
||||||
this.server = server;
|
this.server = server;
|
||||||
|
@ -59,7 +65,13 @@ public class ClientCommunicationHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleTcpMessage(Message msg) {
|
private void handleTcpMessage(Message msg) {
|
||||||
System.out.println("Message received from client " + player.getUsername() + ": " + msg);
|
log.debug("Received TCP message from client \"{}\": {}", player.getUsername(), msg.toString());
|
||||||
|
if (msg instanceof ChunkHashMessage hashMessage) {
|
||||||
|
Chunk chunk = server.getWorld().getChunkAt(new Vector3i(hashMessage.cx(), hashMessage.cy(), hashMessage.cz()));
|
||||||
|
if (chunk != null && hashMessage.hash() != chunk.blockHash()) {
|
||||||
|
sendTcpMessage(new ChunkDataMessage(chunk));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void establishConnection() throws IOException {
|
public void establishConnection() throws IOException {
|
||||||
|
@ -74,19 +86,17 @@ public class ClientCommunicationHandler {
|
||||||
socket.setSoTimeout(0);
|
socket.setSoTimeout(0);
|
||||||
this.clientAddress = socket.getInetAddress();
|
this.clientAddress = socket.getInetAddress();
|
||||||
connectionEstablished = true;
|
connectionEstablished = true;
|
||||||
this.player = server.registerPlayer(this, connectMsg.username());
|
this.player = server.getPlayerManager().register(this, connectMsg.username());
|
||||||
Net.write(new ConnectAcceptMessage(player.getId()), out);
|
Net.write(new ConnectAcceptMessage(player.getId()), out);
|
||||||
System.out.println("Sent connect accept message.");
|
log.debug("Sent connect accept message.");
|
||||||
|
|
||||||
System.out.println("Sending world data...");
|
|
||||||
for (var chunk : server.getWorld().getChunkMap().values()) {
|
for (var chunk : server.getWorld().getChunkMap().values()) {
|
||||||
sendTcpMessage(new ChunkDataMessage(chunk));
|
sendTcpMessage(new ChunkDataMessage(chunk));
|
||||||
}
|
}
|
||||||
System.out.println("Sent all world data.");
|
|
||||||
|
|
||||||
// Initiate a TCP receiver thread to accept incoming messages from the client.
|
// Initiate a TCP receiver thread to accept incoming messages from the client.
|
||||||
TcpReceiver tcpReceiver = new TcpReceiver(in, this::handleTcpMessage)
|
TcpReceiver tcpReceiver = new TcpReceiver(in, this::handleTcpMessage)
|
||||||
.withShutdownHook(() -> server.deregisterPlayer(this.player));
|
.withShutdownHook(() -> server.getPlayerManager().deregister(this.player));
|
||||||
new Thread(tcpReceiver).start();
|
new Thread(tcpReceiver).start();
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
@ -100,7 +110,7 @@ public class ClientCommunicationHandler {
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
System.out.println("Player couldn't connect after " + attempts + " attempts. Aborting.");
|
log.warn("Player couldn't connect after {} attempts. Aborting connection.", attempts);
|
||||||
socket.close();
|
socket.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,9 +138,11 @@ public class ClientCommunicationHandler {
|
||||||
|
|
||||||
public void sendDatagramPacket(DatagramPacket packet) {
|
public void sendDatagramPacket(DatagramPacket packet) {
|
||||||
try {
|
try {
|
||||||
packet.setAddress(clientAddress);
|
if (clientUdpPort != -1) {
|
||||||
packet.setPort(clientUdpPort);
|
packet.setAddress(clientAddress);
|
||||||
datagramSocket.send(packet);
|
packet.setPort(clientUdpPort);
|
||||||
|
datagramSocket.send(packet);
|
||||||
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
package nl.andrewl.aos2_server;
|
||||||
|
|
||||||
|
import nl.andrewl.aos_core.Net;
|
||||||
|
import nl.andrewl.aos_core.net.udp.DatagramInit;
|
||||||
|
import nl.andrewl.aos_core.net.udp.PlayerUpdateMessage;
|
||||||
|
import nl.andrewl.record_net.Message;
|
||||||
|
import org.joml.Vector3f;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.DatagramPacket;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component is responsible for managing the set of players connected to
|
||||||
|
* the server.
|
||||||
|
*/
|
||||||
|
public class PlayerManager {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(PlayerManager.class);
|
||||||
|
|
||||||
|
private final Map<Integer, ServerPlayer> players = new HashMap<>();
|
||||||
|
private final Map<Integer, ClientCommunicationHandler> clientHandlers = new HashMap<>();
|
||||||
|
private int nextClientId = 1;
|
||||||
|
|
||||||
|
public synchronized ServerPlayer register(ClientCommunicationHandler handler, String username) {
|
||||||
|
ServerPlayer player = new ServerPlayer(nextClientId++, username);
|
||||||
|
players.put(player.getId(), player);
|
||||||
|
clientHandlers.put(player.getId(), handler);
|
||||||
|
log.info("Registered player \"{}\" with id {}", player.getUsername(), player.getId());
|
||||||
|
player.setPosition(new Vector3f(0, 64, 0));
|
||||||
|
broadcastUdpMessage(new PlayerUpdateMessage(player));
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void deregister(ServerPlayer player) {
|
||||||
|
ClientCommunicationHandler handler = clientHandlers.get(player.getId());
|
||||||
|
if (handler != null) handler.shutdown();
|
||||||
|
players.remove(player.getId());
|
||||||
|
clientHandlers.remove(player.getId());
|
||||||
|
log.info("Deregistered player \"{}\" with id {}", player.getUsername(), player.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void deregisterAll() {
|
||||||
|
Set<ServerPlayer> playersToDeregister = new HashSet<>(getPlayers());
|
||||||
|
for (var player : playersToDeregister) {
|
||||||
|
deregister(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerPlayer getPlayer(int id) {
|
||||||
|
return players.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<ServerPlayer> getPlayers() {
|
||||||
|
return Collections.unmodifiableCollection(players.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientCommunicationHandler getHandler(int id) {
|
||||||
|
return clientHandlers.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<ClientCommunicationHandler> getHandlers() {
|
||||||
|
return Collections.unmodifiableCollection(clientHandlers.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleUdpInit(DatagramInit init, DatagramPacket packet) {
|
||||||
|
var handler = getHandler(init.clientId());
|
||||||
|
if (handler != null) {
|
||||||
|
handler.setClientUdpPort(packet.getPort());
|
||||||
|
handler.sendDatagramPacket(init);
|
||||||
|
log.debug("Echoed player \"{}\"'s UDP init packet.", getPlayer(init.clientId()).getUsername());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void broadcastUdpMessage(Message msg) {
|
||||||
|
try {
|
||||||
|
byte[] data = Net.write(msg);
|
||||||
|
DatagramPacket packet = new DatagramPacket(data, data.length);
|
||||||
|
for (var handler : getHandlers()) {
|
||||||
|
handler.sendDatagramPacket(packet);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.warn("An error occurred while broadcasting a UDP message.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,37 +1,39 @@
|
||||||
package nl.andrewl.aos2_server;
|
package nl.andrewl.aos2_server;
|
||||||
|
|
||||||
import nl.andrewl.aos_core.model.Chunk;
|
import nl.andrewl.aos_core.model.Chunk;
|
||||||
import nl.andrewl.aos_core.model.Player;
|
|
||||||
import nl.andrewl.aos_core.model.World;
|
import nl.andrewl.aos_core.model.World;
|
||||||
import nl.andrewl.aos_core.net.UdpReceiver;
|
import nl.andrewl.aos_core.net.UdpReceiver;
|
||||||
|
import nl.andrewl.aos_core.net.udp.ClientInputState;
|
||||||
|
import nl.andrewl.aos_core.net.udp.ClientOrientationState;
|
||||||
import nl.andrewl.aos_core.net.udp.DatagramInit;
|
import nl.andrewl.aos_core.net.udp.DatagramInit;
|
||||||
|
import nl.andrewl.aos_core.net.udp.PlayerUpdateMessage;
|
||||||
import nl.andrewl.record_net.Message;
|
import nl.andrewl.record_net.Message;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.*;
|
import java.net.*;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.concurrent.ForkJoinPool;
|
import java.util.concurrent.ForkJoinPool;
|
||||||
|
|
||||||
public class Server implements Runnable {
|
public class Server implements Runnable {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(Server.class);
|
||||||
|
|
||||||
private final ServerSocket serverSocket;
|
private final ServerSocket serverSocket;
|
||||||
private final DatagramSocket datagramSocket;
|
private final DatagramSocket datagramSocket;
|
||||||
private volatile boolean running;
|
private volatile boolean running;
|
||||||
|
|
||||||
private int nextClientId = 1;
|
private final PlayerManager playerManager;
|
||||||
private final Map<Integer, Player> players;
|
|
||||||
private final Map<Integer, ClientCommunicationHandler> playerClientHandlers;
|
|
||||||
private final World world;
|
private final World world;
|
||||||
|
private final WorldUpdater worldUpdater;
|
||||||
|
|
||||||
public Server() throws IOException {
|
public Server() throws IOException {
|
||||||
this.serverSocket = new ServerSocket(24464, 5);
|
this.serverSocket = new ServerSocket(24464, 5);
|
||||||
this.serverSocket.setReuseAddress(true);
|
this.serverSocket.setReuseAddress(true);
|
||||||
this.datagramSocket = new DatagramSocket(24464);
|
this.datagramSocket = new DatagramSocket(24464);
|
||||||
this.datagramSocket.setReuseAddress(true);
|
this.datagramSocket.setReuseAddress(true);
|
||||||
this.players = new HashMap<>();
|
this.playerManager = new PlayerManager();
|
||||||
this.playerClientHandlers = new HashMap<>();
|
this.worldUpdater = new WorldUpdater(this, 20);
|
||||||
|
|
||||||
// Generate world. TODO: do this elsewhere.
|
// Generate world. TODO: do this elsewhere.
|
||||||
Random rand = new Random(1);
|
Random rand = new Random(1);
|
||||||
|
@ -53,14 +55,14 @@ public class Server implements Runnable {
|
||||||
public void run() {
|
public void run() {
|
||||||
running = true;
|
running = true;
|
||||||
new Thread(new UdpReceiver(datagramSocket, this::handleUdpMessage)).start();
|
new Thread(new UdpReceiver(datagramSocket, this::handleUdpMessage)).start();
|
||||||
System.out.println("Started AOS2-Server on TCP/UDP port " + serverSocket.getLocalPort() + "; now accepting connections.");
|
new Thread(worldUpdater).start();
|
||||||
|
log.info("Started AoS2 Server on TCP/UDP port {}; now accepting connections.", serverSocket.getLocalPort());
|
||||||
while (running) {
|
while (running) {
|
||||||
acceptClientConnection();
|
acceptClientConnection();
|
||||||
}
|
}
|
||||||
for (var player : players.values()) {
|
playerManager.deregisterAll();
|
||||||
deregisterPlayer(player);
|
worldUpdater.shutdown();
|
||||||
}
|
datagramSocket.close(); // Shuts down the UdpReceiver.
|
||||||
datagramSocket.close();
|
|
||||||
try {
|
try {
|
||||||
serverSocket.close();
|
serverSocket.close();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
@ -69,44 +71,27 @@ public class Server implements Runnable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleUdpMessage(Message msg, DatagramPacket packet) {
|
public void handleUdpMessage(Message msg, DatagramPacket packet) {
|
||||||
// Echo any init message from known clients.
|
|
||||||
if (msg instanceof DatagramInit init) {
|
if (msg instanceof DatagramInit init) {
|
||||||
var handler = getHandler(init.clientId());
|
playerManager.handleUdpInit(init, packet);
|
||||||
if (handler != null) {
|
} else if (msg instanceof ClientInputState inputState) {
|
||||||
handler.setClientUdpPort(packet.getPort());
|
ServerPlayer player = playerManager.getPlayer(inputState.clientId());
|
||||||
handler.sendDatagramPacket(msg);
|
if (player != null) {
|
||||||
|
player.setLastInputState(inputState);
|
||||||
|
}
|
||||||
|
} else if (msg instanceof ClientOrientationState orientationState) {
|
||||||
|
ServerPlayer player = playerManager.getPlayer(orientationState.clientId());
|
||||||
|
if (player != null) {
|
||||||
|
player.setOrientation(orientationState.x(), orientationState.y());
|
||||||
|
playerManager.broadcastUdpMessage(new PlayerUpdateMessage(player));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized Player registerPlayer(ClientCommunicationHandler handler, String username) {
|
|
||||||
Player player = new Player(nextClientId++, username);
|
|
||||||
players.put(player.getId(), player);
|
|
||||||
playerClientHandlers.put(player.getId(), handler);
|
|
||||||
System.out.println("Registered player " + username + " with id " + player.getId());
|
|
||||||
return player;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void deregisterPlayer(Player player) {
|
|
||||||
ClientCommunicationHandler handler = playerClientHandlers.get(player.getId());
|
|
||||||
handler.shutdown();
|
|
||||||
players.remove(player.getId());
|
|
||||||
playerClientHandlers.remove(player.getId());
|
|
||||||
System.out.println("Deregistered player " + player.getUsername() + " with id " + player.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientCommunicationHandler getHandler(int id) {
|
|
||||||
return playerClientHandlers.get(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public World getWorld() {
|
|
||||||
return world;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void acceptClientConnection() {
|
private void acceptClientConnection() {
|
||||||
try {
|
try {
|
||||||
Socket clientSocket = serverSocket.accept();
|
Socket clientSocket = serverSocket.accept();
|
||||||
var handler = new ClientCommunicationHandler(this, clientSocket, datagramSocket);
|
var handler = new ClientCommunicationHandler(this, clientSocket, datagramSocket);
|
||||||
|
// Establish the connection in a separate thread so that we can continue accepting clients.
|
||||||
ForkJoinPool.commonPool().submit(() -> {
|
ForkJoinPool.commonPool().submit(() -> {
|
||||||
try {
|
try {
|
||||||
handler.establishConnection();
|
handler.establishConnection();
|
||||||
|
@ -122,6 +107,14 @@ public class Server implements Runnable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public World getWorld() {
|
||||||
|
return world;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayerManager getPlayerManager() {
|
||||||
|
return playerManager;
|
||||||
|
}
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
public static void main(String[] args) throws IOException {
|
||||||
new Server().run();
|
new Server().run();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package nl.andrewl.aos2_server;
|
||||||
|
|
||||||
|
import nl.andrewl.aos_core.model.Player;
|
||||||
|
import nl.andrewl.aos_core.net.udp.ClientInputState;
|
||||||
|
|
||||||
|
public class ServerPlayer extends Player {
|
||||||
|
private ClientInputState lastInputState;
|
||||||
|
|
||||||
|
public ServerPlayer(int id, String username) {
|
||||||
|
super(id, username);
|
||||||
|
// Initialize with a default state of no input.
|
||||||
|
lastInputState = new ClientInputState(id, false, false, false, false, false, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientInputState getLastInputState() {
|
||||||
|
return lastInputState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastInputState(ClientInputState inputState) {
|
||||||
|
this.lastInputState = inputState;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
package nl.andrewl.aos2_server;
|
||||||
|
|
||||||
|
import nl.andrewl.aos_core.net.udp.PlayerUpdateMessage;
|
||||||
|
import org.joml.Matrix4f;
|
||||||
|
import org.joml.Vector3f;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A runnable to run as a separate thread, to periodically update the server's
|
||||||
|
* world as players perform actions. This is essentially the "core" of the
|
||||||
|
* game engine, as it controls the game's main update pattern.
|
||||||
|
*/
|
||||||
|
public class WorldUpdater implements Runnable {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(WorldUpdater.class);
|
||||||
|
|
||||||
|
private final Server server;
|
||||||
|
private final float ticksPerSecond;
|
||||||
|
private volatile boolean running;
|
||||||
|
|
||||||
|
public WorldUpdater(Server server, float ticksPerSecond) {
|
||||||
|
this.server = server;
|
||||||
|
this.ticksPerSecond = ticksPerSecond;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void shutdown() {
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
final long nsPerTick = (long) Math.floor((1.0 / ticksPerSecond) * 1_000_000_000.0);
|
||||||
|
log.debug("Running world updater at {} ticks per second, or {} ns per tick.", ticksPerSecond, nsPerTick);
|
||||||
|
running = true;
|
||||||
|
while (running) {
|
||||||
|
long start = System.nanoTime();
|
||||||
|
tick();
|
||||||
|
long elapsedNs = System.nanoTime() - start;
|
||||||
|
if (elapsedNs > nsPerTick) {
|
||||||
|
log.warn("Took {} ns to do one tick, which is more than the desired {} ns per tick.", elapsedNs, nsPerTick);
|
||||||
|
} else {
|
||||||
|
long sleepTime = nsPerTick - elapsedNs;
|
||||||
|
long ms = sleepTime / 1_000_000;
|
||||||
|
int nanos = (int) (sleepTime % 1_000_000);
|
||||||
|
try {
|
||||||
|
Thread.sleep(ms, nanos);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tick() {
|
||||||
|
for (var player : server.getPlayerManager().getPlayers()) {
|
||||||
|
updatePlayerMovement(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePlayerMovement(ServerPlayer player) {
|
||||||
|
boolean updated = false;
|
||||||
|
var v = player.getVelocity();
|
||||||
|
var p = player.getPosition();
|
||||||
|
|
||||||
|
// Apply deceleration to the player before computing any input-derived acceleration.
|
||||||
|
if (v.length() > 0) {
|
||||||
|
Vector3f deceleration = new Vector3f(v).negate().normalize().mul(0.1f);
|
||||||
|
v.add(deceleration);
|
||||||
|
if (v.length() < 0.1f) {
|
||||||
|
v.set(0);
|
||||||
|
}
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3f a = new Vector3f();
|
||||||
|
var inputState = player.getLastInputState();
|
||||||
|
if (inputState.forward()) a.z -= 1;
|
||||||
|
if (inputState.backward()) a.z += 1;
|
||||||
|
if (inputState.left()) a.x -= 1;
|
||||||
|
if (inputState.right()) a.x += 1;
|
||||||
|
if (inputState.jumping()) a.y += 1; // TODO: check if on ground.
|
||||||
|
if (inputState.crouching()) a.y -= 1; // TODO: do crouching instead of down.
|
||||||
|
if (a.lengthSquared() > 0) {
|
||||||
|
a.normalize();
|
||||||
|
Matrix4f moveTransform = new Matrix4f();
|
||||||
|
moveTransform.rotate(player.getOrientation().x, new Vector3f(0, 1, 0));
|
||||||
|
moveTransform.transformDirection(a);
|
||||||
|
v.add(a);
|
||||||
|
final float maxSpeed = 0.25f; // Blocks per tick.
|
||||||
|
if (v.length() > maxSpeed) v.normalize(maxSpeed);
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v.lengthSquared() > 0) {
|
||||||
|
p.add(v);
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updated) {
|
||||||
|
server.getPlayerManager().broadcastUdpMessage(new PlayerUpdateMessage(player));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue