summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorilotterytea <iltsu@alright.party>2025-01-05 00:14:45 +0500
committerilotterytea <iltsu@alright.party>2025-01-05 00:58:57 +0500
commitb0f71c097631f6f9d595865c48f1ffd1990e8794 (patch)
tree0c2a12ba73a9610c6d7aaaf912b08c39f221a4c7
parent47303cced8f6e55aaab2579293da49723ba39c1d (diff)
feat: established communication between game and serverHEADmaster
-rw-r--r--core/src/kz/ilotterytea/maxon/MaxonConstants.java1
-rw-r--r--core/src/kz/ilotterytea/maxon/MaxonGame.java9
-rw-r--r--core/src/kz/ilotterytea/maxon/session/IdentityClient.java9
-rw-r--r--core/src/kz/ilotterytea/maxon/session/SessionClient.java95
-rw-r--r--core/src/kz/ilotterytea/maxon/session/SessionHandlers.java15
-rw-r--r--core/src/main/resources/logback.xml2
-rw-r--r--server/src/kz/ilotterytea/maxon/MaxonServer.java64
-rw-r--r--server/src/kz/ilotterytea/maxon/PlayerConnection.java66
-rw-r--r--server/src/kz/ilotterytea/maxon/ServerHandlers.java27
-rw-r--r--server/src/main/resources/logback.xml2
-rw-r--r--shared/src/kz/ilotterytea/maxon/shared/Acknowledge.java12
-rw-r--r--shared/src/kz/ilotterytea/maxon/shared/Identity.java21
-rw-r--r--shared/src/kz/ilotterytea/maxon/shared/exceptions/PlayerKickException.java15
13 files changed, 333 insertions, 5 deletions
diff --git a/core/src/kz/ilotterytea/maxon/MaxonConstants.java b/core/src/kz/ilotterytea/maxon/MaxonConstants.java
index cab66f4..44c7208 100644
--- a/core/src/kz/ilotterytea/maxon/MaxonConstants.java
+++ b/core/src/kz/ilotterytea/maxon/MaxonConstants.java
@@ -62,6 +62,7 @@ public class MaxonConstants {
public static final String IDENTITY_REFRESH_URL = IDENTITY_BASE_URL + "/refresh";
public static final String IDENTITY_AUTHENTICATION_URL = IDENTITY_BASE_URL + "/authenticate";
public static final String IDENTITY_REGISTRATION_URL = IDENTITY_BASE_URL;
+ public static final String SESSION_WSS_URL = "ws://localhost:31084";
public static final long DISCORD_APPLICATION_ID = 1051092609659052062L;
diff --git a/core/src/kz/ilotterytea/maxon/MaxonGame.java b/core/src/kz/ilotterytea/maxon/MaxonGame.java
index 8986de9..7381b6d 100644
--- a/core/src/kz/ilotterytea/maxon/MaxonGame.java
+++ b/core/src/kz/ilotterytea/maxon/MaxonGame.java
@@ -9,6 +9,7 @@ import kz.ilotterytea.maxon.localization.LocalizationManager;
import kz.ilotterytea.maxon.pets.PetManager;
import kz.ilotterytea.maxon.screens.SplashScreen;
import kz.ilotterytea.maxon.session.IdentityClient;
+import kz.ilotterytea.maxon.session.SessionClient;
import kz.ilotterytea.maxon.utils.GameUpdater;
public class MaxonGame extends Game {
@@ -20,7 +21,9 @@ public class MaxonGame extends Game {
private PetManager petManager;
private DiscordActivityClient discordActivityClient;
+
private IdentityClient identityClient;
+ private SessionClient sessionClient;
private static MaxonGame instance;
@@ -43,6 +46,10 @@ public class MaxonGame extends Game {
return identityClient;
}
+ public SessionClient getSessionClient() {
+ return sessionClient;
+ }
+
public LocalizationManager getLocale() {
return locale;
}
@@ -57,6 +64,8 @@ public class MaxonGame extends Game {
new GameUpdater().checkLatestUpdate();
identityClient = new IdentityClient(Gdx.app.getPreferences("kz.ilotterytea.SigninIdentity"));
+ sessionClient = new SessionClient();
+
batch = new SpriteBatch();
prefs = Gdx.app.getPreferences(MaxonConstants.GAME_APP_PACKAGE);
locale = new LocalizationManager(Gdx.files.internal("i18n/" + prefs.getString("lang", "en_us") + ".json"));
diff --git a/core/src/kz/ilotterytea/maxon/session/IdentityClient.java b/core/src/kz/ilotterytea/maxon/session/IdentityClient.java
index 570b16b..831589d 100644
--- a/core/src/kz/ilotterytea/maxon/session/IdentityClient.java
+++ b/core/src/kz/ilotterytea/maxon/session/IdentityClient.java
@@ -10,6 +10,7 @@ import com.badlogic.gdx.utils.JsonValue;
import com.badlogic.gdx.utils.JsonWriter;
import com.badlogic.gdx.utils.Timer;
import kz.ilotterytea.maxon.MaxonConstants;
+import kz.ilotterytea.maxon.MaxonGame;
import kz.ilotterytea.maxon.utils.RandomUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -90,6 +91,8 @@ public class IdentityClient {
IdentityClient.this.userId = String.valueOf(json.get("data").get("user").getInt("id"));
IdentityClient.this.isAuthorised = true;
log.info("Successfully authorised! Welcome back, {}!", IdentityClient.this.username);
+
+ MaxonGame.getInstance().getSessionClient().connect();
} catch (Exception e) {
log.error("An exception was thrown while authorising", e);
}
@@ -141,6 +144,7 @@ public class IdentityClient {
accessToken = null;
userId = null;
isAuthorised = false;
+ MaxonGame.getInstance().getSessionClient().close(5001, "Failed to validate token");
authorize(username, password);
return;
}
@@ -213,6 +217,8 @@ public class IdentityClient {
sessionPreferences.remove("username");
sessionPreferences.remove("password");
sessionPreferences.flush();
+
+ MaxonGame.getInstance().getSessionClient().close(5001, "Invalidated token");
} catch (Exception ignored) {
}
}
@@ -262,11 +268,14 @@ public class IdentityClient {
userId = null;
isAuthorised = false;
log.warn(error);
+ MaxonGame.getInstance().getSessionClient().close(5002, "Failed to refresh token");
return;
}
accessToken = json.get("data").get("accessToken").asString();
log.info("Token has been refreshed!");
+
+ MaxonGame.getInstance().getSessionClient().updateIdentity();
} catch (Exception e) {
log.error("An exception was thrown while refreshing", e);
}
diff --git a/core/src/kz/ilotterytea/maxon/session/SessionClient.java b/core/src/kz/ilotterytea/maxon/session/SessionClient.java
new file mode 100644
index 0000000..78a6c0e
--- /dev/null
+++ b/core/src/kz/ilotterytea/maxon/session/SessionClient.java
@@ -0,0 +1,95 @@
+package kz.ilotterytea.maxon.session;
+
+import kz.ilotterytea.maxon.MaxonConstants;
+import kz.ilotterytea.maxon.MaxonGame;
+import kz.ilotterytea.maxon.screens.MenuScreen;
+import kz.ilotterytea.maxon.shared.Acknowledge;
+import kz.ilotterytea.maxon.shared.Identity;
+import kz.ilotterytea.maxon.shared.exceptions.PlayerKickException;
+import org.java_websocket.client.WebSocketClient;
+import org.java_websocket.handshake.ServerHandshake;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.URI;
+import java.nio.ByteBuffer;
+
+public class SessionClient extends WebSocketClient {
+ private final Logger log;
+ private final MaxonGame game;
+
+ public SessionClient() {
+ super(URI.create(MaxonConstants.SESSION_WSS_URL));
+ this.log = LoggerFactory.getLogger(SessionClient.class);
+ this.game = MaxonGame.getInstance();
+ }
+
+ @Override
+ public void onOpen(ServerHandshake handshakedata) {
+ log.info("Connected!");
+ updateIdentity();
+ }
+
+ @Override
+ public void onMessage(String message) {
+ }
+
+ @Override
+ public void onMessage(ByteBuffer bytes) {
+ try {
+ // Deserialize the object
+ ByteArrayInputStream bais = new ByteArrayInputStream(bytes.array());
+ ObjectInputStream ois = new ObjectInputStream(bais);
+ Object obj = ois.readObject();
+
+ if (obj instanceof Acknowledge acknowledge) SessionHandlers.handleAcknowledge(acknowledge);
+ else if (obj instanceof PlayerKickException exception) throw exception;
+ } catch (PlayerKickException e) {
+ log.info("Kicked out!", e);
+ } catch (Exception e) {
+ log.error("An exception was thrown while processing message", e);
+ }
+ }
+
+ @Override
+ public void onClose(int code, String reason, boolean remote) {
+ log.info("Connection closed! Reason: {} {}", code, reason);
+ game.getIdentityClient().invalidateToken();
+ if (!game.getScreen().getClass().equals(MenuScreen.class)) {
+ game.setScreen(new MenuScreen());
+ }
+ }
+
+ @Override
+ public void onError(Exception ex) {
+ log.error("Failed to connect", ex);
+ }
+
+ @Override
+ public void connect() {
+ super.connect();
+ }
+
+ public void updateIdentity() {
+ IdentityClient identityClient = game.getIdentityClient();
+
+ Identity identity = new Identity(identityClient.getClientToken(), identityClient.getAccessToken());
+
+ send(identity);
+ }
+
+ public void send(Object object) {
+ try {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(baos);
+ oos.writeObject(object);
+ send(baos.toByteArray());
+ } catch (Exception e) {
+ log.error("Failed to serialize and send an object", e);
+ }
+ }
+}
diff --git a/core/src/kz/ilotterytea/maxon/session/SessionHandlers.java b/core/src/kz/ilotterytea/maxon/session/SessionHandlers.java
new file mode 100644
index 0000000..7b86916
--- /dev/null
+++ b/core/src/kz/ilotterytea/maxon/session/SessionHandlers.java
@@ -0,0 +1,15 @@
+package kz.ilotterytea.maxon.session;
+
+import kz.ilotterytea.maxon.MaxonGame;
+import kz.ilotterytea.maxon.shared.Acknowledge;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SessionHandlers {
+ private static final Logger log = LoggerFactory.getLogger(SessionHandlers.class);
+ private static final SessionClient client = MaxonGame.getInstance().getSessionClient();
+
+ public static void handleAcknowledge(Acknowledge acknowledge) {
+ log.info("alright: {}", acknowledge);
+ }
+}
diff --git a/core/src/main/resources/logback.xml b/core/src/main/resources/logback.xml
index 4b08185..df24417 100644
--- a/core/src/main/resources/logback.xml
+++ b/core/src/main/resources/logback.xml
@@ -9,7 +9,7 @@
</encoder>
</appender>
- <root level="info">
+ <root level="debug">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
diff --git a/server/src/kz/ilotterytea/maxon/MaxonServer.java b/server/src/kz/ilotterytea/maxon/MaxonServer.java
index 7a38a7f..91ba049 100644
--- a/server/src/kz/ilotterytea/maxon/MaxonServer.java
+++ b/server/src/kz/ilotterytea/maxon/MaxonServer.java
@@ -1,36 +1,83 @@
package kz.ilotterytea.maxon;
+import kz.ilotterytea.maxon.shared.Identity;
+import kz.ilotterytea.maxon.shared.exceptions.PlayerKickException;
import org.java_websocket.WebSocket;
+import org.java_websocket.framing.CloseFrame;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.ByteArrayInputStream;
+import java.io.ObjectInputStream;
import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Optional;
public class MaxonServer extends WebSocketServer {
private static MaxonServer instance;
private final Logger log;
+ private final ArrayList<PlayerConnection> connections;
private MaxonServer() {
super(new InetSocketAddress(31084));
this.log = LoggerFactory.getLogger(MaxonServer.class);
+ this.connections = new ArrayList<>();
}
@Override
public void onOpen(WebSocket conn, ClientHandshake handshake) {
- log.info("{} connected!", conn.getRemoteSocketAddress().getAddress().getHostAddress());
+ PlayerConnection connection = new PlayerConnection(conn);
+ log.info("{} ({}) connected!", connection, conn.getRemoteSocketAddress().getAddress().getHostAddress());
+ this.connections.add(connection);
}
@Override
public void onClose(WebSocket conn, int code, String reason, boolean remote) {
- log.info("Connection {} has been closed! ({} {} {})", conn, code, reason, remote);
+ Optional<PlayerConnection> connection = this.connections.stream().filter((x) -> x.getConnection().equals(conn)).findFirst();
+ if (connection.isPresent()) {
+ log.info("{} has left! Reason: {} {}", connection.get(), code, reason);
+ this.connections.remove(connection.get());
+ } else {
+ log.info("Unknown connection was closed! Reason: {} {}", code, reason);
+ }
}
@Override
public void onMessage(WebSocket conn, String message) {
- log.info("{} says {}", conn, message);
+ this.connections.removeIf((x) -> x.getConnection().equals(conn));
+ conn.send("Invalid input.");
+ conn.close(CloseFrame.UNEXPECTED_CONDITION);
+ }
+
+ @Override
+ public void onMessage(WebSocket conn, ByteBuffer message) {
+ Optional<PlayerConnection> playerConnection = this.connections.stream().filter((x) -> x.getConnection().equals(conn)).findFirst();
+
+ if (playerConnection.isEmpty()) {
+ conn.close(5001, "Your PlayerConnection was not found!");
+ return;
+ }
+
+ PlayerConnection c = playerConnection.get();
+
+ try {
+ // Deserialize the object
+ ByteArrayInputStream bais = new ByteArrayInputStream(message.array());
+ ObjectInputStream ois = new ObjectInputStream(bais);
+ Object obj = ois.readObject();
+
+ if (obj instanceof Identity identity) ServerHandlers.handleIdentity(c, identity);
+ else kickConnection(c, PlayerKickException.internalServerError());
+ } catch (PlayerKickException e) {
+ kickConnection(c, e);
+ } catch (Exception e) {
+ log.error("An exception was thrown while processing message", e);
+ kickConnection(c, PlayerKickException.internalServerError());
+ }
}
@Override
@@ -45,8 +92,19 @@ public class MaxonServer extends WebSocketServer {
setConnectionLostTimeout(100);
}
+ public void kickConnection(PlayerConnection connection, PlayerKickException e) {
+ connection.send(e);
+ connection.getConnection().close();
+ this.connections.remove(connection);
+ log.debug("Kicked out {}! Reason: {}", connection, e.getMessage());
+ }
+
public static MaxonServer getInstance() {
if (instance == null) instance = new MaxonServer();
return instance;
}
+
+ public ArrayList<PlayerConnection> getPlayerConnections() {
+ return connections;
+ }
}
diff --git a/server/src/kz/ilotterytea/maxon/PlayerConnection.java b/server/src/kz/ilotterytea/maxon/PlayerConnection.java
new file mode 100644
index 0000000..79b46d0
--- /dev/null
+++ b/server/src/kz/ilotterytea/maxon/PlayerConnection.java
@@ -0,0 +1,66 @@
+package kz.ilotterytea.maxon;
+
+import kz.ilotterytea.maxon.shared.Identity;
+import org.java_websocket.WebSocket;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.sql.Timestamp;
+
+public class PlayerConnection {
+ private static int TOTAL_CONNECTION_IDS = 0;
+
+ private final int id;
+ private final WebSocket connection;
+ private Identity identity;
+
+ private final Timestamp connectedTimestamp;
+
+ public PlayerConnection(WebSocket connection) {
+ this.connection = connection;
+ this.connectedTimestamp = new Timestamp(System.currentTimeMillis());
+
+ this.id = TOTAL_CONNECTION_IDS;
+ TOTAL_CONNECTION_IDS++;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public WebSocket getConnection() {
+ return connection;
+ }
+
+ public Identity getIdentity() {
+ return identity;
+ }
+
+ public void setIdentity(Identity identity) {
+ this.identity = identity;
+ }
+
+ public Timestamp getConnectedTimestamp() {
+ return connectedTimestamp;
+ }
+
+ public void send(Object object) {
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+
+ try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream)) {
+ objectOutputStream.writeObject(object);
+ } catch (IOException ignored) {
+
+ }
+
+ this.connection.send(byteArrayOutputStream.toByteArray());
+ }
+
+ @Override
+ public String toString() {
+ return "PlayerConnection{" +
+ "id=" + id +
+ '}';
+ }
+}
diff --git a/server/src/kz/ilotterytea/maxon/ServerHandlers.java b/server/src/kz/ilotterytea/maxon/ServerHandlers.java
new file mode 100644
index 0000000..43468b3
--- /dev/null
+++ b/server/src/kz/ilotterytea/maxon/ServerHandlers.java
@@ -0,0 +1,27 @@
+package kz.ilotterytea.maxon;
+
+import kz.ilotterytea.maxon.shared.Acknowledge;
+import kz.ilotterytea.maxon.shared.Identity;
+import kz.ilotterytea.maxon.shared.exceptions.PlayerKickException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ServerHandlers {
+ private static final Logger log = LoggerFactory.getLogger(ServerHandlers.class);
+ private static final MaxonServer server = MaxonServer.getInstance();
+
+ public static void handleIdentity(PlayerConnection connection, Identity identity) {
+ if (server.getPlayerConnections()
+ .stream()
+ .filter((x) -> x.getIdentity() != null)
+ .anyMatch((x) -> x.getIdentity().equals(identity) && x.getId() != connection.getId())
+ ) {
+ server.kickConnection(connection, PlayerKickException.loggedIn());
+ return;
+ }
+
+ connection.setIdentity(identity);
+ connection.send(new Acknowledge(identity));
+ log.debug("Successfully identified {} for {}", identity, connection);
+ }
+}
diff --git a/server/src/main/resources/logback.xml b/server/src/main/resources/logback.xml
index 4b08185..df24417 100644
--- a/server/src/main/resources/logback.xml
+++ b/server/src/main/resources/logback.xml
@@ -9,7 +9,7 @@
</encoder>
</appender>
- <root level="info">
+ <root level="debug">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
diff --git a/shared/src/kz/ilotterytea/maxon/shared/Acknowledge.java b/shared/src/kz/ilotterytea/maxon/shared/Acknowledge.java
new file mode 100644
index 0000000..b64d3e2
--- /dev/null
+++ b/shared/src/kz/ilotterytea/maxon/shared/Acknowledge.java
@@ -0,0 +1,12 @@
+package kz.ilotterytea.maxon.shared;
+
+import java.io.Serializable;
+
+public record Acknowledge(Object payload) implements Serializable {
+ @Override
+ public String toString() {
+ return "Acknowledge{" +
+ "payload='" + payload + '\'' +
+ '}';
+ }
+}
diff --git a/shared/src/kz/ilotterytea/maxon/shared/Identity.java b/shared/src/kz/ilotterytea/maxon/shared/Identity.java
new file mode 100644
index 0000000..103c53c
--- /dev/null
+++ b/shared/src/kz/ilotterytea/maxon/shared/Identity.java
@@ -0,0 +1,21 @@
+package kz.ilotterytea.maxon.shared;
+
+import java.io.Serializable;
+
+public record Identity(String accessToken, String clientToken) implements Serializable {
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof Identity i) {
+ return i.accessToken.equals(accessToken) && i.clientToken.equals(clientToken);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "Identity{" +
+ "clientToken='" + clientToken + '\'' +
+ ", accessToken='" + accessToken + '\'' +
+ '}';
+ }
+}
diff --git a/shared/src/kz/ilotterytea/maxon/shared/exceptions/PlayerKickException.java b/shared/src/kz/ilotterytea/maxon/shared/exceptions/PlayerKickException.java
new file mode 100644
index 0000000..45d55b3
--- /dev/null
+++ b/shared/src/kz/ilotterytea/maxon/shared/exceptions/PlayerKickException.java
@@ -0,0 +1,15 @@
+package kz.ilotterytea.maxon.shared.exceptions;
+
+public class PlayerKickException extends RuntimeException {
+ public PlayerKickException(String message) {
+ super(message);
+ }
+
+ public static PlayerKickException loggedIn() {
+ return new PlayerKickException("You logged in from another location");
+ }
+
+ public static PlayerKickException internalServerError() {
+ return new PlayerKickException("Internal Server Error");
+ }
+}