diff options
14 files changed, 394 insertions, 5 deletions
diff --git a/core/build.gradle b/core/build.gradle index bb73187..e142af4 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -4,9 +4,10 @@ eclipse.project.name = appName + '-core' dependencies { api "com.badlogicgames.gdx:gdx:$gdxVersion" api "com.github.mgsx-dev.gdx-gltf:gltf:$gdxGltfVersion" + api "org.java-websocket:Java-WebSocket:$javaWebsocketVersion" api project(':shared') - if(enableGraalNative == 'true') { + if (enableGraalNative == 'true') { implementation "io.github.berstanio:gdx-svmhelper-annotations:$graalHelperVersion" } } diff --git a/core/src/main/java/kz/ilotterytea/frogartha/FrogarthaConstants.java b/core/src/main/java/kz/ilotterytea/frogartha/FrogarthaConstants.java new file mode 100644 index 0000000..f80948a --- /dev/null +++ b/core/src/main/java/kz/ilotterytea/frogartha/FrogarthaConstants.java @@ -0,0 +1,7 @@ +package kz.ilotterytea.frogartha; + +public class FrogarthaConstants { + public static class URLS { + public static String SESSION_WSS = "ws://localhost:20015"; + } +} diff --git a/core/src/main/java/kz/ilotterytea/frogartha/FrogarthaGame.java b/core/src/main/java/kz/ilotterytea/frogartha/FrogarthaGame.java index 149dcc7..ea227ec 100644 --- a/core/src/main/java/kz/ilotterytea/frogartha/FrogarthaGame.java +++ b/core/src/main/java/kz/ilotterytea/frogartha/FrogarthaGame.java @@ -2,6 +2,7 @@ package kz.ilotterytea.frogartha; import com.badlogic.gdx.Game; import kz.ilotterytea.frogartha.screens.GameScreen; +import kz.ilotterytea.frogartha.sessions.SessionClient; /** * {@link com.badlogic.gdx.ApplicationListener} implementation shared by all platforms. @@ -9,8 +10,13 @@ import kz.ilotterytea.frogartha.screens.GameScreen; public class FrogarthaGame extends Game { private static FrogarthaGame instance; + private SessionClient sessionClient; + @Override public void create() { + sessionClient = new SessionClient(); + sessionClient.connect(); + setScreen(new GameScreen()); } @@ -18,4 +24,8 @@ public class FrogarthaGame extends Game { if (instance == null) instance = new FrogarthaGame(); return instance; } + + public SessionClient getSessionClient() { + return sessionClient; + } } diff --git a/core/src/main/java/kz/ilotterytea/frogartha/sessions/SessionClient.java b/core/src/main/java/kz/ilotterytea/frogartha/sessions/SessionClient.java new file mode 100644 index 0000000..c775fdf --- /dev/null +++ b/core/src/main/java/kz/ilotterytea/frogartha/sessions/SessionClient.java @@ -0,0 +1,81 @@ +package kz.ilotterytea.frogartha.sessions; + +import kz.ilotterytea.frogartha.FrogarthaConstants; +import kz.ilotterytea.frogartha.FrogarthaGame; +import kz.ilotterytea.frogartha.domain.Acknowledge; +import kz.ilotterytea.frogartha.domain.Identity; +import kz.ilotterytea.frogartha.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 FrogarthaGame game; + + public SessionClient() { + super(URI.create(FrogarthaConstants.URLS.SESSION_WSS)); + this.log = LoggerFactory.getLogger(SessionClient.class); + this.game = FrogarthaGame.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) SessionHandlers.handleAcknowledge((Acknowledge) obj); + else if (obj instanceof PlayerKickException) throw (PlayerKickException) obj; + } catch (PlayerKickException e) { + log.info("Kicked out: {}", e.getMessage()); + } 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); + } + + @Override + public void onError(Exception ex) { + log.error("Failed to connect", ex); + } + + 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); + } + } + + public void updateIdentity() { + send(new Identity("Playerxd")); + } +} diff --git a/core/src/main/java/kz/ilotterytea/frogartha/sessions/SessionHandlers.java b/core/src/main/java/kz/ilotterytea/frogartha/sessions/SessionHandlers.java new file mode 100644 index 0000000..8c7487b --- /dev/null +++ b/core/src/main/java/kz/ilotterytea/frogartha/sessions/SessionHandlers.java @@ -0,0 +1,15 @@ +package kz.ilotterytea.frogartha.sessions; + +import kz.ilotterytea.frogartha.FrogarthaGame; +import kz.ilotterytea.frogartha.domain.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 = FrogarthaGame.getInstance().getSessionClient(); + + public static void handleAcknowledge(Acknowledge acknowledge) { + log.info("The server was acknowledged: {}", acknowledge); + } +} diff --git a/gradle.properties b/gradle.properties index 8d571e2..e4e9538 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,4 +7,5 @@ gwtFrameworkVersion=2.11.0 gwtPluginVersion=1.1.29 enableGraalNative=false gdxVersion=1.13.1 +javaWebsocketVersion=1.6.0 projectVersion=1.0.0 diff --git a/server/build.gradle b/server/build.gradle index 1aaf851..d4acfbd 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -4,7 +4,7 @@ apply plugin: 'application' java.sourceCompatibility = 11 java.targetCompatibility = 11 if (JavaVersion.current().isJava9Compatible()) { - compileJava.options.release.set(11) + compileJava.options.release.set(11) } mainClassName = 'kz.ilotterytea.frogartha.server.ServerLauncher' @@ -12,6 +12,7 @@ application.setMainClass(mainClassName) eclipse.project.name = appName + '-server' dependencies { + api "org.java-websocket:Java-WebSocket:$javaWebsocketVersion" implementation project(':shared') } diff --git a/server/src/main/java/kz/ilotterytea/frogartha/server/FrogarthaServer.java b/server/src/main/java/kz/ilotterytea/frogartha/server/FrogarthaServer.java new file mode 100644 index 0000000..f6a9847 --- /dev/null +++ b/server/src/main/java/kz/ilotterytea/frogartha/server/FrogarthaServer.java @@ -0,0 +1,110 @@ +package kz.ilotterytea.frogartha.server; + +import kz.ilotterytea.frogartha.domain.Identity; +import kz.ilotterytea.frogartha.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 FrogarthaServer extends WebSocketServer { + private static FrogarthaServer instance; + + private final Logger log; + private final ArrayList<PlayerConnection> players; + + private FrogarthaServer() { + super(new InetSocketAddress(20015)); + this.log = LoggerFactory.getLogger(FrogarthaServer.class); + this.players = new ArrayList<>(); + } + + @Override + public void onOpen(WebSocket webSocket, ClientHandshake clientHandshake) { + PlayerConnection player = new PlayerConnection(webSocket); + log.info("{} ({}) connected!", player, webSocket.getRemoteSocketAddress().getAddress().getHostAddress()); + this.players.add(player); + } + + @Override + public void onClose(WebSocket webSocket, int i, String s, boolean b) { + Optional<PlayerConnection> player = this.players.stream().filter((x) -> x.getConnection().equals(webSocket)).findFirst(); + + if (player.isPresent()) { + log.info("{} has let! Reason: {} {}", player.get(), i, s); + } else { + log.info("Unknown connection was closed! Reason: {} {}", i, s); + } + } + + @Override + public void onMessage(WebSocket webSocket, String s) { + this.players.removeIf((x) -> x.getConnection().equals(webSocket)); + webSocket.send("Invalid input."); + webSocket.close(CloseFrame.UNEXPECTED_CONDITION); + } + + @Override + public void onMessage(WebSocket conn, ByteBuffer message) { + Optional<PlayerConnection> optionalPlayer = this.players.stream().filter((x) -> x.getConnection().equals(conn)).findFirst(); + + if (optionalPlayer.isEmpty()) { + conn.close(); + return; + } + + PlayerConnection player = optionalPlayer.get(); + + try { + // Deserializing the object + ByteArrayInputStream bais = new ByteArrayInputStream(message.array()); + ObjectInputStream ois = new ObjectInputStream(bais); + Object obj = ois.readObject(); + + if (obj instanceof Identity) ServerHandlers.handleIdentity(player, (Identity) obj); + else throw PlayerKickException.internalServerError(); + } catch (PlayerKickException e) { + kickConnection(player, e); + } catch (Exception e) { + log.error("An exception was thrown while processing message", e); + kickConnection(player, PlayerKickException.internalServerError()); + } + } + + @Override + public void onError(WebSocket webSocket, Exception e) { + log.error("Something went error", e); + } + + @Override + public void onStart() { + log.info("Running the server on port {}", getPort()); + setConnectionLostTimeout(0); + setConnectionLostTimeout(100); + } + + public void kickConnection(PlayerConnection player, PlayerKickException exception) { + player.send(exception); + player.getConnection().close(); + players.remove(player); + log.debug("Kicked out {}! Reason: {}", player, exception.getMessage()); + } + + public static FrogarthaServer getInstance() { + if (instance == null) instance = new FrogarthaServer(); + return instance; + } + + public ArrayList<PlayerConnection> getPlayers() { + return players; + } +} diff --git a/server/src/main/java/kz/ilotterytea/frogartha/server/PlayerConnection.java b/server/src/main/java/kz/ilotterytea/frogartha/server/PlayerConnection.java new file mode 100644 index 0000000..c62dce5 --- /dev/null +++ b/server/src/main/java/kz/ilotterytea/frogartha/server/PlayerConnection.java @@ -0,0 +1,67 @@ +package kz.ilotterytea.frogartha.server; + +import kz.ilotterytea.frogartha.domain.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.id = TOTAL_CONNECTION_IDS; + TOTAL_CONNECTION_IDS++; + + this.connectedTimestamp = new Timestamp(System.currentTimeMillis()); + this.identity = null; + } + + public void send(Object object) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + try (ObjectOutputStream oos = new ObjectOutputStream(baos)) { + oos.writeObject(object); + } catch (IOException ignored) { + + } + + this.connection.send(baos.toByteArray()); + } + + public int getId() { + return id; + } + + public WebSocket getConnection() { + return connection; + } + + public Timestamp getConnectedTimestamp() { + return connectedTimestamp; + } + + public Identity getIdentity() { + return identity; + } + + public void setIdentity(Identity identity) { + this.identity = identity; + } + + @Override + public String toString() { + return "PlayerConnection{" + + "id=" + id + + '}'; + } +} diff --git a/server/src/main/java/kz/ilotterytea/frogartha/server/ServerHandlers.java b/server/src/main/java/kz/ilotterytea/frogartha/server/ServerHandlers.java new file mode 100644 index 0000000..87dc17d --- /dev/null +++ b/server/src/main/java/kz/ilotterytea/frogartha/server/ServerHandlers.java @@ -0,0 +1,26 @@ +package kz.ilotterytea.frogartha.server; + +import kz.ilotterytea.frogartha.domain.Acknowledge; +import kz.ilotterytea.frogartha.domain.Identity; +import kz.ilotterytea.frogartha.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 FrogarthaServer server = FrogarthaServer.getInstance(); + + public static void handleIdentity(PlayerConnection player, Identity identity) { + if (server.getPlayers() + .stream() + .filter((x) -> x.getIdentity() != null) + .anyMatch((x) -> x.getIdentity().equals(identity) && x.getId() != player.getId())) { + server.kickConnection(player, PlayerKickException.loggedIn()); + return; + } + + player.setIdentity(identity); + player.send(new Acknowledge(player)); + log.debug("Successfully identified {} for {}", identity, player); + } +} diff --git a/server/src/main/java/kz/ilotterytea/frogartha/server/ServerLauncher.java b/server/src/main/java/kz/ilotterytea/frogartha/server/ServerLauncher.java index fc7c1e5..8c07f64 100644 --- a/server/src/main/java/kz/ilotterytea/frogartha/server/ServerLauncher.java +++ b/server/src/main/java/kz/ilotterytea/frogartha/server/ServerLauncher.java @@ -1,8 +1,11 @@ package kz.ilotterytea.frogartha.server; -/** Launches the server application. */ +/** + * Launches the server application. + */ public class ServerLauncher { public static void main(String[] args) { - // TODO Implement server application. + FrogarthaServer server = FrogarthaServer.getInstance(); + server.start(); } -}
\ No newline at end of file +} diff --git a/shared/src/main/java/kz/ilotterytea/frogartha/domain/Acknowledge.java b/shared/src/main/java/kz/ilotterytea/frogartha/domain/Acknowledge.java new file mode 100644 index 0000000..3460cbc --- /dev/null +++ b/shared/src/main/java/kz/ilotterytea/frogartha/domain/Acknowledge.java @@ -0,0 +1,22 @@ +package kz.ilotterytea.frogartha.domain; + +import java.io.Serializable; + +public class Acknowledge implements Serializable { + private final Object payload; + + public Acknowledge(Object payload) { + this.payload = payload; + } + + public Object getPayload() { + return payload; + } + + @Override + public String toString() { + return "Acknowledge{" + + "payload=" + payload + + '}'; + } +} diff --git a/shared/src/main/java/kz/ilotterytea/frogartha/domain/Identity.java b/shared/src/main/java/kz/ilotterytea/frogartha/domain/Identity.java new file mode 100644 index 0000000..9a72268 --- /dev/null +++ b/shared/src/main/java/kz/ilotterytea/frogartha/domain/Identity.java @@ -0,0 +1,30 @@ +package kz.ilotterytea.frogartha.domain; + +import java.io.Serializable; + +public class Identity implements Serializable { + private final String username; + + public Identity(String username) { + this.username = username; + } + + public String getUsername() { + return username; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Identity) { + return ((Identity) obj).username.equals(username); + } + return false; + } + + @Override + public String toString() { + return "Identity{" + + "username='" + username + '\'' + + '}'; + } +} diff --git a/shared/src/main/java/kz/ilotterytea/frogartha/exceptions/PlayerKickException.java b/shared/src/main/java/kz/ilotterytea/frogartha/exceptions/PlayerKickException.java new file mode 100644 index 0000000..a6d8c0a --- /dev/null +++ b/shared/src/main/java/kz/ilotterytea/frogartha/exceptions/PlayerKickException.java @@ -0,0 +1,15 @@ +package kz.ilotterytea.frogartha.exceptions; + +public class PlayerKickException extends RuntimeException { + private 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"); + } +} |
