From 582bd83a2d28f10407de91f83be452ad06e32074 Mon Sep 17 00:00:00 2001 From: punchready Date: Tue, 10 Aug 2021 19:31:16 +0200 Subject: [PATCH] wip: experimental version --- .idea/runConfigurations.xml | 10 + .../{Server.java => ServerApplication.java} | 57 ++- .../marvelous/server/args/ServerArgs.java | 9 +- .../marvelous/server/game/GameSession.java | 240 +++++++++++ .../game/pipelining/DisconnectSegment.java | 23 + .../FilterEndRoundRequestSegment.java | 41 ++ .../pipelining/GameLogicSegment.java | 19 +- .../{lobby => game}/pipelining/Packet.java | 13 +- .../pipelining/PauseSegment.java | 59 +-- .../{lobby => game}/pipelining/Pipeline.java | 6 +- .../pipelining/PlayerFilterSegment.java | 23 +- .../pipelining/RequestGameStateSegment.java | 23 +- .../{lobby => game}/pipelining/Segment.java | 4 +- .../marvelous/server/lobby/LifetimeTimer.java | 30 -- .../marvelous/server/lobby/Lobby.java | 364 ---------------- .../marvelous/server/lobby/TimeoutTimer.java | 91 ---- .../server/lobby/TurnTimeoutTimer.java | 56 --- .../lobby/pipelining/DisconnectSegment.java | 41 -- .../FilterEndRoundRequestSegment.java | 54 --- .../server/lobbymanager/LobbyConnection.java | 326 --------------- .../lobbymanager/LobbyConnectionState.java | 7 - .../server/lobbymanager/LobbyManager.java | 273 ------------ .../server/lobbymanager/LobbyRunner.java | 76 ---- .../server/lobbymanager/Participant.java | 62 --- .../server/lobbymanager/ParticipantState.java | 7 - .../lobbymanager/RandomWordGenerator.java | 139 ------- .../teamname/marvelous/server/net/Client.java | 104 +++++ .../{netconnector => net}/ClientState.java | 3 +- .../marvelous/server/net/MarvelousServer.java | 88 ++++ .../server/{netconnector => net}/SUID.java | 2 +- .../marvelous/server/net/ServerSession.java | 393 ++++++++++++++++++ .../marvelous/server/net/SessionState.java | 6 + .../marvelous/server/net/SocketEvent.java | 19 + .../marvelous/server/net/SocketEventType.java | 7 + .../marvelous/server/netconnector/Client.java | 58 --- .../server/netconnector/MarvelousServer.java | 42 -- .../server/netconnector/UserManager.java | 278 ------------- .../marvelous/server/BaseGameLogicTest.java | 14 - ...va => MarvelousServerApplicationTest.java} | 132 +++--- .../server/lobby/TurnTimeoutTimerTest.java | 31 -- .../pipelining/DisconnectSegmentTest.java | 103 ----- .../FilterEndRoundRequestSegmentTest.java | 76 ---- .../pipelining/GameLogicSegmentTest.java | 46 -- .../server/lobby/pipelining/PacketTest.java | 57 --- .../lobby/pipelining/PauseSegmentTest.java | 106 ----- .../server/lobby/pipelining/PipelineTest.java | 66 --- .../pipelining/PlayerFilterSegmentTest.java | 118 ------ .../RequestGameLogicSegmentTest.java | 51 --- .../lobbymanager/RandomWordGeneratorTest.java | 17 - .../server/netconnector/ClientTest.java | 53 --- .../server/netconnector/UserManagerTest.java | 63 --- 51 files changed, 1089 insertions(+), 2897 deletions(-) create mode 100644 .idea/runConfigurations.xml rename Server/src/main/java/uulm/teamname/marvelous/server/{Server.java => ServerApplication.java} (83%) create mode 100644 Server/src/main/java/uulm/teamname/marvelous/server/game/GameSession.java create mode 100644 Server/src/main/java/uulm/teamname/marvelous/server/game/pipelining/DisconnectSegment.java create mode 100644 Server/src/main/java/uulm/teamname/marvelous/server/game/pipelining/FilterEndRoundRequestSegment.java rename Server/src/main/java/uulm/teamname/marvelous/server/{lobby => game}/pipelining/GameLogicSegment.java (71%) rename Server/src/main/java/uulm/teamname/marvelous/server/{lobby => game}/pipelining/Packet.java (86%) rename Server/src/main/java/uulm/teamname/marvelous/server/{lobby => game}/pipelining/PauseSegment.java (72%) rename Server/src/main/java/uulm/teamname/marvelous/server/{lobby => game}/pipelining/Pipeline.java (94%) rename Server/src/main/java/uulm/teamname/marvelous/server/{lobby => game}/pipelining/PlayerFilterSegment.java (70%) rename Server/src/main/java/uulm/teamname/marvelous/server/{lobby => game}/pipelining/RequestGameStateSegment.java (56%) rename Server/src/main/java/uulm/teamname/marvelous/server/{lobby => game}/pipelining/Segment.java (90%) delete mode 100644 Server/src/main/java/uulm/teamname/marvelous/server/lobby/LifetimeTimer.java delete mode 100644 Server/src/main/java/uulm/teamname/marvelous/server/lobby/Lobby.java delete mode 100644 Server/src/main/java/uulm/teamname/marvelous/server/lobby/TimeoutTimer.java delete mode 100644 Server/src/main/java/uulm/teamname/marvelous/server/lobby/TurnTimeoutTimer.java delete mode 100644 Server/src/main/java/uulm/teamname/marvelous/server/lobby/pipelining/DisconnectSegment.java delete mode 100644 Server/src/main/java/uulm/teamname/marvelous/server/lobby/pipelining/FilterEndRoundRequestSegment.java delete mode 100644 Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/LobbyConnection.java delete mode 100644 Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/LobbyConnectionState.java delete mode 100644 Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/LobbyManager.java delete mode 100644 Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/LobbyRunner.java delete mode 100644 Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/Participant.java delete mode 100644 Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/ParticipantState.java delete mode 100644 Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/RandomWordGenerator.java create mode 100644 Server/src/main/java/uulm/teamname/marvelous/server/net/Client.java rename Server/src/main/java/uulm/teamname/marvelous/server/{netconnector => net}/ClientState.java (61%) create mode 100644 Server/src/main/java/uulm/teamname/marvelous/server/net/MarvelousServer.java rename Server/src/main/java/uulm/teamname/marvelous/server/{netconnector => net}/SUID.java (94%) create mode 100644 Server/src/main/java/uulm/teamname/marvelous/server/net/ServerSession.java create mode 100644 Server/src/main/java/uulm/teamname/marvelous/server/net/SessionState.java create mode 100644 Server/src/main/java/uulm/teamname/marvelous/server/net/SocketEvent.java create mode 100644 Server/src/main/java/uulm/teamname/marvelous/server/net/SocketEventType.java delete mode 100644 Server/src/main/java/uulm/teamname/marvelous/server/netconnector/Client.java delete mode 100644 Server/src/main/java/uulm/teamname/marvelous/server/netconnector/MarvelousServer.java delete mode 100644 Server/src/main/java/uulm/teamname/marvelous/server/netconnector/UserManager.java rename Server/src/test/java/uulm/teamname/marvelous/server/{MarvelousServerTest.java => MarvelousServerApplicationTest.java} (64%) delete mode 100644 Server/src/test/java/uulm/teamname/marvelous/server/lobby/TurnTimeoutTimerTest.java delete mode 100644 Server/src/test/java/uulm/teamname/marvelous/server/lobby/pipelining/DisconnectSegmentTest.java delete mode 100644 Server/src/test/java/uulm/teamname/marvelous/server/lobby/pipelining/FilterEndRoundRequestSegmentTest.java delete mode 100644 Server/src/test/java/uulm/teamname/marvelous/server/lobby/pipelining/GameLogicSegmentTest.java delete mode 100644 Server/src/test/java/uulm/teamname/marvelous/server/lobby/pipelining/PacketTest.java delete mode 100644 Server/src/test/java/uulm/teamname/marvelous/server/lobby/pipelining/PauseSegmentTest.java delete mode 100644 Server/src/test/java/uulm/teamname/marvelous/server/lobby/pipelining/PipelineTest.java delete mode 100644 Server/src/test/java/uulm/teamname/marvelous/server/lobby/pipelining/PlayerFilterSegmentTest.java delete mode 100644 Server/src/test/java/uulm/teamname/marvelous/server/lobby/pipelining/RequestGameLogicSegmentTest.java delete mode 100644 Server/src/test/java/uulm/teamname/marvelous/server/lobbymanager/RandomWordGeneratorTest.java delete mode 100644 Server/src/test/java/uulm/teamname/marvelous/server/netconnector/ClientTest.java delete mode 100644 Server/src/test/java/uulm/teamname/marvelous/server/netconnector/UserManagerTest.java diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..a1fe99e --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/Server.java b/Server/src/main/java/uulm/teamname/marvelous/server/ServerApplication.java similarity index 83% rename from Server/src/main/java/uulm/teamname/marvelous/server/Server.java rename to Server/src/main/java/uulm/teamname/marvelous/server/ServerApplication.java index 75f8930..e0432fd 100644 --- a/Server/src/main/java/uulm/teamname/marvelous/server/Server.java +++ b/Server/src/main/java/uulm/teamname/marvelous/server/ServerApplication.java @@ -11,7 +11,8 @@ import uulm.teamname.marvelous.gamelibrary.config.ScenarioConfig; import uulm.teamname.marvelous.gamelibrary.json.JSON; import uulm.teamname.marvelous.gamelibrary.json.ValidationUtility; import uulm.teamname.marvelous.server.args.ServerArgs; -import uulm.teamname.marvelous.server.netconnector.MarvelousServer; +import uulm.teamname.marvelous.server.net.MarvelousServer; +import uulm.teamname.marvelous.server.net.ServerSession; import java.io.*; import java.net.InetSocketAddress; @@ -25,20 +26,22 @@ import java.util.Map; * {@code -c .\configs\marvelheros.character.json -m .\configs\matchconfig_1.game.json * -s .\configs\asgard.scenario.json -v} into the arguments field. */ -public class Server { +public class ServerApplication { private static PartyConfig partyConfig; private static ScenarioConfig scenarioConfig; private static CharacterConfig characterConfig; - private static Integer maxLobbies; + private static ServerSession session; public static void main(String[] args) { + Thread.currentThread().setName("Main"); + ServerArgs serverArgs = new ServerArgs(); JCommander jc = JCommander.newBuilder().addObject(serverArgs).build(); try { jc.parse(args); - } catch (ParameterException e) { + }catch(ParameterException e) { Logger.error("Invalid parameters: {}", e.getMessage()); System.exit(1); return; @@ -50,13 +53,11 @@ public class Server { return; } - maxLobbies = serverArgs.getMaxLobbies(); - - if (serverArgs.isVerbose() || serverArgs.isCheckConfig()) { + if(serverArgs.isVerbose() || serverArgs.isCheckConfig()) { // If checkConfig, the LogLevel is also set to max, because more information // is exactly what checking the requirements means setLogLevel(5); - } else { + }else { setLogLevel(serverArgs.getLogLevel()); } @@ -72,25 +73,33 @@ public class Server { } Logger.info("populating static Server variables with config objects"); - Server.scenarioConfig = scenarioConfig; - Server.characterConfig = characterConfig; - Server.partyConfig = partyConfig; + ServerApplication.scenarioConfig = scenarioConfig; + ServerApplication.characterConfig = characterConfig; + ServerApplication.partyConfig = partyConfig; InetSocketAddress address = new InetSocketAddress(serverArgs.getPort()); Logger.trace("Inet address {} created", address); + Logger.trace("Instantiating ServerSession..."); + session = new ServerSession(); + Logger.trace("Instantiating MarvelousServer..."); - MarvelousServer netConnector = new MarvelousServer(address); + MarvelousServer server = new MarvelousServer(address); Logger.trace("Starting MarvelousServer..."); - netConnector.start(); + server.start(); + + Logger.trace("Starting ServerSession..."); + getSession().run(); Logger.trace("End of Main reached. Exiting main thread."); } - /** Function that sets the log level for {@link Logger Tinylog}. + /** + * Function that sets the log level for {@link Logger Tinylog}. * It has to be executed BEFORE ANY LOGGING OPERATIONS . */ + @SuppressWarnings("DuplicateBranchesInSwitch") private static void setLogLevel(int logLevel) { Map map = new HashMap<>(); @@ -144,18 +153,21 @@ public class Server { } int grassFields = 0; - + int portals = 0; for (FieldType[] row: config.get().scenario) { for (FieldType type: row) { if (type == FieldType.GRASS) grassFields++; + if (type == FieldType.PORTAL) portals++; } - if (grassFields > 18) break; } - if (grassFields <= 18) { - Logger.error( - "Scenario Configuration vas invalid: Only {} grass fields found, which is less than 18" - , grassFields); + if (grassFields < 20) { + Logger.error("Scenario Configuration vas invalid: Only {} grass fields found, which is less than 20", grassFields); + System.exit(1); + } + + if (portals < 2) { + Logger.error("Scenario Configuration vas invalid: Only {} portals found, which is less than 2", portals); System.exit(1); } @@ -231,8 +243,7 @@ public class Server { return characterConfig; } - /** Returns the maximum amount of lobbies the server should create */ - public static Integer getMaxLobbies() { - return maxLobbies; + public static ServerSession getSession() { + return session; } } diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/args/ServerArgs.java b/Server/src/main/java/uulm/teamname/marvelous/server/args/ServerArgs.java index d06a6fa..e7f987b 100644 --- a/Server/src/main/java/uulm/teamname/marvelous/server/args/ServerArgs.java +++ b/Server/src/main/java/uulm/teamname/marvelous/server/args/ServerArgs.java @@ -4,6 +4,7 @@ import com.beust.jcommander.Parameter; import java.io.File; +@SuppressWarnings({"FieldMayBeFinal", "unused"}) public class ServerArgs { /** Whether help is requested */ @Parameter(names = {"-h", "--help", "-?", "--?", "-H"}, help = true) @@ -41,9 +42,6 @@ public class ServerArgs { @Parameter(names = {"--replay", "-r"}, description = "Path for replay files to be saved at") private String folderPath; - @Parameter(names = {"--max-lobbies", "-L", "--team25-max-lobbies"}, description = "Maximum amount of lobbies that can be created") - private int maxLobbies = 8; - /** Whether help is requested */ public boolean isHelp() { return help; @@ -89,11 +87,6 @@ public class ServerArgs { return folderPath; } - /** The maximum mount of lobbies automatically created */ - public int getMaxLobbies() { - return maxLobbies; - } - @Override public String toString() { diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/game/GameSession.java b/Server/src/main/java/uulm/teamname/marvelous/server/game/GameSession.java new file mode 100644 index 0000000..3c541bb --- /dev/null +++ b/Server/src/main/java/uulm/teamname/marvelous/server/game/GameSession.java @@ -0,0 +1,240 @@ +package uulm.teamname.marvelous.server.game; + +import uulm.teamname.marvelous.gamelibrary.ArrayTools; +import uulm.teamname.marvelous.gamelibrary.config.CharacterProperties; +import uulm.teamname.marvelous.gamelibrary.events.Event; +import uulm.teamname.marvelous.gamelibrary.events.EventBuilder; +import uulm.teamname.marvelous.gamelibrary.events.EventType; +import uulm.teamname.marvelous.gamelibrary.gamelogic.GameInstance; +import uulm.teamname.marvelous.gamelibrary.messages.ParticipantType; +import uulm.teamname.marvelous.gamelibrary.messages.server.EventMessage; +import uulm.teamname.marvelous.gamelibrary.messages.server.GameStructureMessage; +import uulm.teamname.marvelous.gamelibrary.messages.server.GeneralAssignmentMessage; +import uulm.teamname.marvelous.gamelibrary.requests.Request; +import uulm.teamname.marvelous.server.ServerApplication; +import uulm.teamname.marvelous.server.game.pipelining.*; +import uulm.teamname.marvelous.server.net.Client; +import uulm.teamname.marvelous.server.net.ClientState; + +import java.util.*; + +public class GameSession { + public final String id = UUID.randomUUID().toString(); + + public final HashMap characterChoices = new HashMap<>(); + private final HashMap characterIndices = new HashMap<>(); + private final HashMap> characterSelection = new HashMap<>(); + + private final HashMap badRequests = new HashMap<>(); + + private boolean started = false; + + private GameInstance instance; + private boolean paused = false; + + private Pipeline pipeline; + + public GameSession() { + List characters = ArrayTools.toArrayList(ServerApplication.getCharacterConfig().characters); + characterChoices.put(ParticipantType.PlayerOne, new CharacterProperties[characters.size() / 2]); + characterChoices.put(ParticipantType.PlayerTwo, new CharacterProperties[characters.size() / 2]); + characterIndices.put(ParticipantType.PlayerOne, new Integer[characters.size() / 2]); + characterIndices.put(ParticipantType.PlayerTwo, new Integer[characters.size() / 2]); + int n1 = 0; + int n2 = 0; + int i = 0; + Collections.shuffle(characters); + for(CharacterProperties character: characters) { + if(i < characters.size() / 2) { + characterChoices.get(ParticipantType.PlayerOne)[n1] = character; + characterIndices.get(ParticipantType.PlayerOne)[n1] = character.characterID; + n1++; + }else { + characterChoices.get(ParticipantType.PlayerTwo)[n2] = character; + characterIndices.get(ParticipantType.PlayerTwo)[n2] = character.characterID; + n2++; + } + i++; + } + } + + public void setPaused(boolean paused) { + this.paused = paused; + } + + public boolean getPaused() { + return paused; + } + + public GameInstance getInstance() { + return instance; + } + + public void addSpectator(Client client) { + GeneralAssignmentMessage response = new GeneralAssignmentMessage(); + response.gameID = id; + client.sendMessage(response); + + client.sendMessage(getGameStructure(client.getType())); + } + + public void handleReconnect(Client client) { + GeneralAssignmentMessage response = new GeneralAssignmentMessage(); + response.gameID = id; + client.sendMessage(response); + + client.sendMessage(getGameStructure(client.getType())); + } + + public void handleSelection(Client client, boolean[] selection) { + ArrayList indices = new ArrayList<>(); + for(int i = 0; i < selection.length; i++) { + if(selection[i]) { + indices.add(characterIndices.get(client.getType())[i]); + } + } + characterSelection.put(client.getType(), indices); + } + + public void handleRequests(Client client, Request[] messages) { + if(client.getType() == ParticipantType.Spectator || paused) { + return; + } + + Optional> resultingEvents = pipeline.processRequests(client, messages); + + if(resultingEvents.isEmpty()) { + reject(client); + }else { + accept(client, resultingEvents.get()); + + if(instance.state.isWon()) { + ServerApplication.getSession().reset(); + } + } + } + + public void handleDisconnect(Client client) { + if(started && client.getType() != ParticipantType.Spectator) { + winner(client.getType() == ParticipantType.PlayerOne ? ParticipantType.PlayerTwo : ParticipantType.PlayerOne); + } + } + + + public void start() { + initialize(); + + List start = instance.startGame(characterSelection.get(ParticipantType.PlayerOne), characterSelection.get(ParticipantType.PlayerTwo)); + + EventMessage message = new EventMessage(); + message.messages = start.toArray(new Event[0]); + ServerApplication.getSession().broadcast(message); + + started = true; + } + + private void initialize() { + pipeline = new Pipeline(); + + var reqSegment = new RequestGameStateSegment(this); + var pauseSegment = new PauseSegment(this); + var filterEndRoundRequestSegment = new FilterEndRoundRequestSegment(this); + var disconnectSegment = new DisconnectSegment(); + var playerFilterSegment = new PlayerFilterSegment(); + var gameLogicSegment = new GameLogicSegment(this); + pipeline.addSegment(reqSegment) + .addSegment(pauseSegment) + .addSegment(filterEndRoundRequestSegment) + .addSegment(disconnectSegment) + .addSegment(playerFilterSegment) + .addSegment(gameLogicSegment); + + instance = new GameInstance(ServerApplication.getPartyConfig(), ServerApplication.getCharacterConfig(), ServerApplication.getScenarioConfig()); + + for(Client player: ServerApplication.getSession().getPlayers()) { + player.setState(ClientState.Playing); + player.sendMessage(getGameStructure(player.getType())); + } + GameStructureMessage spectators = getGameStructure(ParticipantType.Spectator); + for(Client spectator: ServerApplication.getSession().getSpectators()) { + spectator.setState(ClientState.Playing); + spectator.sendMessage(spectators); + } + } + + private void accept(Client source, List accepted) { + if(accepted.isEmpty()) { + return; + } + + accepted.add(instance.getGameStateEvent()); + + EventMessage message = new EventMessage(); + message.messages = accepted.toArray(new Event[0]); + ServerApplication.getSession().broadcast(message, source.getID()); + + accepted.add(0, new EventBuilder(EventType.Ack).buildGameEvent()); + + EventMessage response = new EventMessage(); + response.messages = accepted.toArray(new Event[0]); + source.sendMessage(response); + + badRequests.replace(source.getType(), 0); + } + + private void reject(Client source) { + EventMessage response = new EventMessage(); + response.messages = new Event[]{ + new EventBuilder(EventType.Nack).buildGameEvent(), + instance.getGameStateEvent() + }; + source.sendMessage(response); + + int bad = badRequests.getOrDefault(source.getType(), 0) + 1; + badRequests.replace(source.getType(), bad); + + if(bad >= 5) { + winner(source.getType() == ParticipantType.PlayerOne ? ParticipantType.PlayerTwo : ParticipantType.PlayerOne); + } + } + + private synchronized void winner(ParticipantType winner) { + EventMessage message = new EventMessage(); + message.messages = new Event[] { + new EventBuilder(EventType.WinEvent) + .withPlayerWon(winner.equals(ParticipantType.PlayerOne) ? 1 : 2) + .buildGameEvent(), + new EventBuilder(EventType.DisconnectEvent) + .buildGameEvent() + }; + + ServerApplication.getSession().broadcast(message); + ServerApplication.getSession().reset(); + } + + + private GameStructureMessage getGameStructure(ParticipantType assignment) { + GameStructureMessage message = new GameStructureMessage(); + message.assignment = assignment; + message.matchconfig = ServerApplication.getPartyConfig(); + message.scenarioconfig = ServerApplication.getScenarioConfig(); + + var characters = ServerApplication.getCharacterConfig().getIDMap(); + for(Client player: ServerApplication.getSession().getPlayers()) { + int n = 0; + CharacterProperties[] selected = new CharacterProperties[characters.size() / 4]; + for(Integer i: characterSelection.get(player.getType())) { + selected[n++] = characters.get(i); + } + if(player.getType() == ParticipantType.PlayerOne) { + message.playerOneName = player.getID().getName(); + message.playerOneCharacters = selected; + }else { + message.playerTwoName = player.getID().getName(); + message.playerTwoCharacters = selected; + } + } + + return message; + } +} diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/game/pipelining/DisconnectSegment.java b/Server/src/main/java/uulm/teamname/marvelous/server/game/pipelining/DisconnectSegment.java new file mode 100644 index 0000000..eb7c460 --- /dev/null +++ b/Server/src/main/java/uulm/teamname/marvelous/server/game/pipelining/DisconnectSegment.java @@ -0,0 +1,23 @@ +package uulm.teamname.marvelous.server.game.pipelining; + +import org.tinylog.Logger; +import uulm.teamname.marvelous.gamelibrary.events.Event; +import uulm.teamname.marvelous.gamelibrary.requests.RequestType; +import uulm.teamname.marvelous.server.game.GameSession; + +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * The {@link DisconnectSegment} handles requests of {@link RequestType} DisconnectRequest. + */ +public class DisconnectSegment implements Segment { + @Override + public void processRequests(Packet packet, List carrier, AtomicBoolean abort) { + Logger.trace("DisconnectSegment received {} requests.", packet.size()); + if(packet.containsRequestOfType(RequestType.DisconnectRequest)) { + packet.getOrigin().disconnect(); + packet.clear(); + } + } +} diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/game/pipelining/FilterEndRoundRequestSegment.java b/Server/src/main/java/uulm/teamname/marvelous/server/game/pipelining/FilterEndRoundRequestSegment.java new file mode 100644 index 0000000..6956ace --- /dev/null +++ b/Server/src/main/java/uulm/teamname/marvelous/server/game/pipelining/FilterEndRoundRequestSegment.java @@ -0,0 +1,41 @@ +package uulm.teamname.marvelous.server.game.pipelining; + +import org.tinylog.Logger; +import uulm.teamname.marvelous.gamelibrary.entities.EntityType; +import uulm.teamname.marvelous.gamelibrary.events.Event; +import uulm.teamname.marvelous.gamelibrary.gamelogic.GameInstance; +import uulm.teamname.marvelous.gamelibrary.messages.ParticipantType; +import uulm.teamname.marvelous.gamelibrary.requests.RequestType; +import uulm.teamname.marvelous.server.game.GameSession; + +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * {@link Segment} that checks for an {@link RequestType#EndRoundRequest}, and + * if it exists, checks for the player that has sent it. If the player sending + * this request is not the active player, it flags the message as an error. + */ +public class FilterEndRoundRequestSegment implements Segment { + + private final GameSession parent; + + public FilterEndRoundRequestSegment(GameSession parent) { + this.parent = parent; + } + + @Override + public void processRequests(Packet packet, List carrier, AtomicBoolean abort) { + Logger.trace("FilterEndRoundSegment has received {} requests", packet.size()); + if(packet.containsRequestOfType(RequestType.EndRoundRequest)) { + Logger.trace("Packet contains EndRoundRequest"); + + var active = parent.getInstance().state.getActiveCharacter().type; + var from = packet.getOrigin().getType() == ParticipantType.PlayerOne ? EntityType.P1 : EntityType.P2; + if(active != from) { + Logger.trace("Invalid endRoundRequest. Expected {} but got {}. Aborting...", active, from); + abort.set(true); + } + } + } +} diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/pipelining/GameLogicSegment.java b/Server/src/main/java/uulm/teamname/marvelous/server/game/pipelining/GameLogicSegment.java similarity index 71% rename from Server/src/main/java/uulm/teamname/marvelous/server/lobby/pipelining/GameLogicSegment.java rename to Server/src/main/java/uulm/teamname/marvelous/server/game/pipelining/GameLogicSegment.java index 42593cf..9094a6f 100644 --- a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/pipelining/GameLogicSegment.java +++ b/Server/src/main/java/uulm/teamname/marvelous/server/game/pipelining/GameLogicSegment.java @@ -1,35 +1,36 @@ -package uulm.teamname.marvelous.server.lobby.pipelining; +package uulm.teamname.marvelous.server.game.pipelining; import org.tinylog.Logger; import uulm.teamname.marvelous.gamelibrary.events.Event; import uulm.teamname.marvelous.gamelibrary.gamelogic.GameInstance; import uulm.teamname.marvelous.gamelibrary.requests.Request; +import uulm.teamname.marvelous.server.game.GameSession; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; /** - * The {@link GameLogicSegment} handles all {@link GameInstance game} relevant {@link Request Requests}. Therefore it - * updates the game with the matching request. + * The {@link GameLogicSegment} handles all {@link GameInstance game} relevant {@link Request Requests}. */ public class GameLogicSegment implements Segment { - private GameInstance game; + private final GameSession parent; - public GameLogicSegment(GameInstance game) { - this.game = game; + public GameLogicSegment(GameSession parent) { + this.parent = parent; } @Override public void processRequests(Packet packet, List carrier, AtomicBoolean abort) { Logger.trace("GameStateSegment received {} requests.", packet.size()); - var result = game.checkRequestsAndApply(packet); + + var result = parent.getInstance().checkRequestsAndApply(packet); Logger.trace("GameLogic generated {} events", result.map(List::size).orElse(0)); - if (result.isPresent()) { + if(result.isPresent()) { Logger.trace("Result from GameLogic is present. Adding requests to carrier."); carrier.addAll(result.get()); packet.clear(); - } else { + }else { Logger.debug("Result from GameLogic is invalid. Triggering error."); abort.set(true); } diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/pipelining/Packet.java b/Server/src/main/java/uulm/teamname/marvelous/server/game/pipelining/Packet.java similarity index 86% rename from Server/src/main/java/uulm/teamname/marvelous/server/lobby/pipelining/Packet.java rename to Server/src/main/java/uulm/teamname/marvelous/server/game/pipelining/Packet.java index 649209f..79ab834 100644 --- a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/pipelining/Packet.java +++ b/Server/src/main/java/uulm/teamname/marvelous/server/game/pipelining/Packet.java @@ -1,20 +1,20 @@ -package uulm.teamname.marvelous.server.lobby.pipelining; +package uulm.teamname.marvelous.server.game.pipelining; import uulm.teamname.marvelous.gamelibrary.requests.Request; import uulm.teamname.marvelous.gamelibrary.requests.RequestType; -import uulm.teamname.marvelous.server.lobbymanager.Participant; +import uulm.teamname.marvelous.server.net.Client; import java.util.*; /** - * The {@link Packet} contains all {@link Request Requests} and the belonging {@link Participant}. You can add and + * The {@link Packet} contains all {@link Request Requests} and the belonging {@link Client}. You can add and * remove {@link Request Requests} at will. */ public class Packet extends ArrayList { - private final Participant origin; + private final Client origin; - public Packet(Request[] requests, Participant origin) { + public Packet(Request[] requests, Client origin) { this.origin = origin; addAll(Arrays.asList(requests)); } @@ -55,7 +55,7 @@ public class Packet extends ArrayList { this.removeIf(request -> !listOfTypes.contains(request.type)); } - public Participant getOrigin() { + public Client getOrigin() { return origin; } @@ -69,6 +69,7 @@ public class Packet extends ArrayList { } @Override + @SuppressWarnings("MethodDoesntCallSuperMethod") public Object clone() { return new Packet(this.toArray(new Request[0]), this.origin); } diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/pipelining/PauseSegment.java b/Server/src/main/java/uulm/teamname/marvelous/server/game/pipelining/PauseSegment.java similarity index 72% rename from Server/src/main/java/uulm/teamname/marvelous/server/lobby/pipelining/PauseSegment.java rename to Server/src/main/java/uulm/teamname/marvelous/server/game/pipelining/PauseSegment.java index 1a9a5f8..89ba818 100644 --- a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/pipelining/PauseSegment.java +++ b/Server/src/main/java/uulm/teamname/marvelous/server/game/pipelining/PauseSegment.java @@ -1,4 +1,4 @@ -package uulm.teamname.marvelous.server.lobby.pipelining; +package uulm.teamname.marvelous.server.game.pipelining; import org.tinylog.Logger; import uulm.teamname.marvelous.gamelibrary.events.Event; @@ -7,6 +7,7 @@ import uulm.teamname.marvelous.gamelibrary.events.EventType; import uulm.teamname.marvelous.gamelibrary.messages.ParticipantType; import uulm.teamname.marvelous.gamelibrary.requests.RequestBuilder; import uulm.teamname.marvelous.gamelibrary.requests.RequestType; +import uulm.teamname.marvelous.server.game.GameSession; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; @@ -16,35 +17,12 @@ import java.util.concurrent.atomic.AtomicBoolean; */ public class PauseSegment implements Segment { - private boolean paused; + private final GameSession parent; - public PauseSegment() { - paused = false; + public PauseSegment(GameSession parent) { + this.parent = parent; } - /** This method pauses the game if it is not already paused.*/ - public void pauseGame() { - if (!paused) { - paused = true; - Logger.debug("Game paused."); - } - } - - /** - * This method ends a paused game. - */ - public void pauseEnd() { - if (paused) { - paused = false; - Logger.debug("Game unpaused."); - } - } - - public boolean isPaused() { - return paused; - } - - /** * Pipelining method to process a set of requests. The list of requests will be processed according to the following * rules: @@ -68,47 +46,46 @@ public class PauseSegment implements Segment { public void processRequests(Packet packet, List carrier, AtomicBoolean abort) { Logger.trace("PauseSegment received {} requests. PausedState is {}", packet.size(), - paused); + parent.getPaused()); + // check if there is a pause request (either start or stop) - if (packet.contains(new RequestBuilder(RequestType.PauseStartRequest).buildGameRequest())) { + if(packet.contains(new RequestBuilder(RequestType.PauseStartRequest).buildGameRequest())) { Logger.trace("PauseStartRequest found"); - if(packet.getOrigin().type == ParticipantType.Spectator || packet.getOrigin().isAI) { + if(packet.getOrigin().getType() == ParticipantType.Spectator || packet.getOrigin().isAI()) { Logger.trace("Invalid pause start request. Aborting"); abort.set(true); return; } - if (!paused) { - // pause the game - pauseGame(); + if(!parent.getPaused()) { + parent.setPaused(true); // create a new PauseStartEvent carrier.add(new EventBuilder(EventType.PauseStartEvent).buildGameEvent()); Logger.trace("Added PauseStartEvent to pipeline carrier"); - } else { // if the game is already paused + }else { // if the game is already paused Logger.info("PauseStartRequest sent even though the game wasn't paused. Error triggered."); abort.set(true); return; } - } else if (packet.contains(new RequestBuilder(RequestType.PauseStopRequest).buildGameRequest())) { + }else if(packet.contains(new RequestBuilder(RequestType.PauseStopRequest).buildGameRequest())) { Logger.trace("PauseStopRequest found"); - if(packet.getOrigin().type == ParticipantType.Spectator || packet.getOrigin().isAI) { + if(packet.getOrigin().getType() == ParticipantType.Spectator || packet.getOrigin().isAI()) { Logger.trace("Invalid pause stop request. Aborting"); abort.set(true); return; } - if (paused) { - pauseEnd(); - Logger.debug("Game unpaused."); + if(parent.getPaused()) { + parent.setPaused(false); // create a new PauseStartRequest carrier.add(new EventBuilder(EventType.PauseStopEvent).buildGameEvent()); Logger.trace("Added PauseStopEvent to pipeline carrier"); - } else { // if the game is not paused + }else { // if the game is not paused Logger.info("PauseStopRequest sent even though the game wasn't paused. Error triggered."); abort.set(true); return; } } - if (paused) { + if(parent.getPaused()) { Logger.trace("As the game is paused, Requests are removed."); packet.removeRequestsOfTypes( RequestType.MeleeAttackRequest, diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/pipelining/Pipeline.java b/Server/src/main/java/uulm/teamname/marvelous/server/game/pipelining/Pipeline.java similarity index 94% rename from Server/src/main/java/uulm/teamname/marvelous/server/lobby/pipelining/Pipeline.java rename to Server/src/main/java/uulm/teamname/marvelous/server/game/pipelining/Pipeline.java index 6b7c518..e30690d 100644 --- a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/pipelining/Pipeline.java +++ b/Server/src/main/java/uulm/teamname/marvelous/server/game/pipelining/Pipeline.java @@ -1,9 +1,9 @@ -package uulm.teamname.marvelous.server.lobby.pipelining; +package uulm.teamname.marvelous.server.game.pipelining; import org.tinylog.Logger; import uulm.teamname.marvelous.gamelibrary.events.Event; import uulm.teamname.marvelous.gamelibrary.requests.Request; -import uulm.teamname.marvelous.server.lobbymanager.Participant; +import uulm.teamname.marvelous.server.net.Client; import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; @@ -37,7 +37,7 @@ public class Pipeline { * first check whether the {@link Optional} is empty by doing {@link Optional#isEmpty()} or {@link * Optional#isPresent()}, and act accordingly. */ - public Optional> processRequests(Request[] requests, Participant origin) { + public Optional> processRequests(Client origin, Request[] requests) { Logger.trace("Pipeline started RequestProcessing"); // The packet carries the requests, and gets smaller per segment Packet packet = new Packet(requests, origin); diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/pipelining/PlayerFilterSegment.java b/Server/src/main/java/uulm/teamname/marvelous/server/game/pipelining/PlayerFilterSegment.java similarity index 70% rename from Server/src/main/java/uulm/teamname/marvelous/server/lobby/pipelining/PlayerFilterSegment.java rename to Server/src/main/java/uulm/teamname/marvelous/server/game/pipelining/PlayerFilterSegment.java index 801f661..5911438 100644 --- a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/pipelining/PlayerFilterSegment.java +++ b/Server/src/main/java/uulm/teamname/marvelous/server/game/pipelining/PlayerFilterSegment.java @@ -1,11 +1,11 @@ -package uulm.teamname.marvelous.server.lobby.pipelining; +package uulm.teamname.marvelous.server.game.pipelining; import org.tinylog.Logger; import uulm.teamname.marvelous.gamelibrary.entities.EntityType; import uulm.teamname.marvelous.gamelibrary.events.Event; import uulm.teamname.marvelous.gamelibrary.requests.CharacterRequest; import uulm.teamname.marvelous.gamelibrary.requests.Request; -import uulm.teamname.marvelous.server.lobbymanager.Participant; +import uulm.teamname.marvelous.server.net.Client; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; @@ -14,8 +14,9 @@ public class PlayerFilterSegment implements Segment { @Override public void processRequests(Packet packet, List carrier, AtomicBoolean abort) { Logger.trace("PlayerFilterSegment received {} requests", packet.size()); - for (Request request: packet) { - boolean valid = switch (request.type) { + + for(Request request: packet) { + boolean valid = switch(request.type) { case MeleeAttackRequest, RangedAttackRequest, MoveRequest, @@ -23,7 +24,7 @@ public class PlayerFilterSegment implements Segment { UseInfinityStoneRequest -> isValid((CharacterRequest) request, packet.getOrigin()); default -> true; }; - if (!valid) { + if(!valid) { Logger.debug("Invalid request of type {} of enemy player found, setting abort flag", request.type); abort.set(true); break; @@ -31,21 +32,17 @@ public class PlayerFilterSegment implements Segment { } } - private boolean isValid(CharacterRequest request, Participant participant) { - EntityType type = switch(participant.type) { + private boolean isValid(CharacterRequest request, Client client) { + EntityType type = switch(client.getType()) { case PlayerOne -> EntityType.P1; case PlayerTwo -> EntityType.P2; case Spectator -> null; }; - if (type == null) { - Logger.warn("Some spectator-sent movement requests arrived in the PlayerFilterSegment.\n" + - "Have you ordered your segments properly?"); + if(type == null) { return false; - } else { + }else { return request.originEntity.type.equals(type); } - - } } diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/pipelining/RequestGameStateSegment.java b/Server/src/main/java/uulm/teamname/marvelous/server/game/pipelining/RequestGameStateSegment.java similarity index 56% rename from Server/src/main/java/uulm/teamname/marvelous/server/lobby/pipelining/RequestGameStateSegment.java rename to Server/src/main/java/uulm/teamname/marvelous/server/game/pipelining/RequestGameStateSegment.java index ec09d1b..ce9a87c 100644 --- a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/pipelining/RequestGameStateSegment.java +++ b/Server/src/main/java/uulm/teamname/marvelous/server/game/pipelining/RequestGameStateSegment.java @@ -1,35 +1,36 @@ -package uulm.teamname.marvelous.server.lobby.pipelining; +package uulm.teamname.marvelous.server.game.pipelining; import org.tinylog.Logger; import uulm.teamname.marvelous.gamelibrary.events.Event; import uulm.teamname.marvelous.gamelibrary.gamelogic.GameInstance; import uulm.teamname.marvelous.gamelibrary.messages.server.EventMessage; import uulm.teamname.marvelous.gamelibrary.requests.RequestType; +import uulm.teamname.marvelous.server.game.GameSession; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; /** - * The {@link RequestGameStateSegment} handles requests of {@link RequestType} Req. Therefore it sends the active - * gamestate and clears the {@link Packet} afterwards. + * The {@link RequestGameStateSegment} handles requests of {@link RequestType} Req. */ public class RequestGameStateSegment implements Segment { - private final GameInstance game; + private final GameSession parent; - public RequestGameStateSegment(GameInstance game) { - this.game = game; + public RequestGameStateSegment(GameSession parent) { + this.parent = parent; } @Override public void processRequests(Packet packet, List carrier, AtomicBoolean abort) { Logger.trace("RequestGameStateSegment received {} requests", packet.size()); - if (packet.containsRequestOfType(RequestType.Req)) { - Logger.trace("Req event found. Returning Gamestate, and clearing entire RequestList"); - var gamestateEventMessage = new EventMessage(); - gamestateEventMessage.messages = new Event[] {game.getGameStateEvent()}; - packet.getOrigin().sendMessage(gamestateEventMessage); + + if(packet.containsRequestOfType(RequestType.Req)) { + Logger.trace("Req event found. Returning GameState, and clearing entire RequestList"); + EventMessage message = new EventMessage(); + message.messages = new Event[] {parent.getInstance().getGameStateEvent()}; + packet.getOrigin().sendMessage(message); carrier.clear(); packet.clear(); } diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/pipelining/Segment.java b/Server/src/main/java/uulm/teamname/marvelous/server/game/pipelining/Segment.java similarity index 90% rename from Server/src/main/java/uulm/teamname/marvelous/server/lobby/pipelining/Segment.java rename to Server/src/main/java/uulm/teamname/marvelous/server/game/pipelining/Segment.java index fc42dce..4b695e2 100644 --- a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/pipelining/Segment.java +++ b/Server/src/main/java/uulm/teamname/marvelous/server/game/pipelining/Segment.java @@ -1,4 +1,4 @@ -package uulm.teamname.marvelous.server.lobby.pipelining; +package uulm.teamname.marvelous.server.game.pipelining; import uulm.teamname.marvelous.gamelibrary.events.Event; import uulm.teamname.marvelous.gamelibrary.requests.Request; @@ -21,5 +21,5 @@ public interface Segment { * segment, but instead an error in the events passed to it, like for example moving into a Rock. The * conventional way of setting this boolean is to write {@code abort.set(true); return;} */ - public void processRequests(Packet packet, List carrier, AtomicBoolean abort); + void processRequests(Packet packet, List carrier, AtomicBoolean abort); } diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/LifetimeTimer.java b/Server/src/main/java/uulm/teamname/marvelous/server/lobby/LifetimeTimer.java deleted file mode 100644 index c6d2317..0000000 --- a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/LifetimeTimer.java +++ /dev/null @@ -1,30 +0,0 @@ -package uulm.teamname.marvelous.server.lobby; - -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; - -/** - * A timer meant to time the entire Lifetime of the lobby, and if it is higher than the maximum permitted value, - * call a callback given by the lobby to compute a winner, and quit the lobby. - */ -public class LifetimeTimer { - - private final ScheduledExecutorService timer; - private final int maxGameTime; - private final Runnable callback; - - LifetimeTimer(int maxGameTime, Runnable callback) { - String lobbyThreadName = Thread.currentThread().getName(); - ThreadFactory threadFactory = r -> new Thread(r, lobbyThreadName + "-LifetimeTimerThread"); - this.timer = Executors.newSingleThreadScheduledExecutor(threadFactory); - - this.maxGameTime = maxGameTime; - this.callback = callback; - } - - void startTimer() { - timer.schedule(callback, maxGameTime, TimeUnit.SECONDS); - } -} diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/Lobby.java b/Server/src/main/java/uulm/teamname/marvelous/server/lobby/Lobby.java deleted file mode 100644 index 6565c3e..0000000 --- a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/Lobby.java +++ /dev/null @@ -1,364 +0,0 @@ -package uulm.teamname.marvelous.server.lobby; - -import org.tinylog.Logger; -import uulm.teamname.marvelous.gamelibrary.entities.EntityID; -import uulm.teamname.marvelous.gamelibrary.entities.EntityType; -import uulm.teamname.marvelous.gamelibrary.events.Event; -import uulm.teamname.marvelous.gamelibrary.events.EventBuilder; -import uulm.teamname.marvelous.gamelibrary.events.EventType; -import uulm.teamname.marvelous.gamelibrary.gamelogic.GameInstance; -import uulm.teamname.marvelous.gamelibrary.messages.ParticipantType; -import uulm.teamname.marvelous.gamelibrary.config.CharacterConfig; -import uulm.teamname.marvelous.gamelibrary.config.PartyConfig; -import uulm.teamname.marvelous.gamelibrary.config.ScenarioConfig; -import uulm.teamname.marvelous.gamelibrary.requests.Request; -import uulm.teamname.marvelous.server.lobby.pipelining.*; -import uulm.teamname.marvelous.server.lobbymanager.LobbyConnection; -import uulm.teamname.marvelous.server.lobbymanager.LobbyManager; -import uulm.teamname.marvelous.server.lobbymanager.Participant; - -import java.util.*; - -public class Lobby { - private final String gameID; - private final LobbyConnection connection; - private final GameInstance game; - private final Pipeline pipeline; - private Participant activePlayer; - private int badRequests; - private PauseSegment pauseSegment; - private final TurnTimeoutTimer turnTimeoutTimer; - private final TimeoutTimer timeoutTimer; - private final LifetimeTimer lifetimeTimer; - - /** - * The {@link Lobby} is where the magic happens. In this class is a whole {@link GameInstance game} is processed. To - * initialize the game it gets the following parameters - * - * @param gameID a String to identify the game - * @param connection the Connection to the {@link LobbyManager} - * @param partyConfig declared in Editor - * @param characterConfig declared in Editor - * @param scenarioConfig declared in Editor - */ - public Lobby( - String gameID, - LobbyConnection connection, - PartyConfig partyConfig, - CharacterConfig characterConfig, - ScenarioConfig scenarioConfig, - List player1Characters, - List player2Characters - ) { - this.gameID = gameID; - this.connection = connection; - - this.game = new GameInstance( - partyConfig, - characterConfig, - scenarioConfig - ); - - this.pipeline = new Pipeline(); - - var reqSegment = new RequestGameStateSegment(this.game); - this.pauseSegment = new PauseSegment(); - var filterEndRoundRequestSegment = new FilterEndRoundRequestSegment(game.state::getActiveCharacter); - var disconnectSegment = new DisconnectSegment(this); - var playerFilterSegment = new PlayerFilterSegment(); - var gameLogicSegment = new GameLogicSegment(this.game); - - pipeline.addSegment(reqSegment) - .addSegment(pauseSegment) - .addSegment(filterEndRoundRequestSegment) - .addSegment(disconnectSegment) - .addSegment(playerFilterSegment) - .addSegment(gameLogicSegment); - - Logger.trace("Instantiating timers..."); - - this.turnTimeoutTimer = new TurnTimeoutTimer(partyConfig.maxRoundTime, this::turnTimeout); - updateTurnTimer(); - - this.timeoutTimer = new TimeoutTimer(partyConfig.maxResponseTime, this::soonTimeout, this::timeout); - refreshTimeoutTimer(connection.getPlayer1()); - refreshTimeoutTimer(connection.getPlayer2()); - - this.lifetimeTimer = new LifetimeTimer( - partyConfig.maxGameTime, - () -> triggerWin(getParticipantForEntityType(game.state.getCurrentOvertimeWinner()).get())); - - this.connection.broadcastEvents(this.game.startGame(player1Characters, player2Characters)); - - } - - /** - * Called by {@link LobbyConnection} to handle requests - * - * @param requests to be processed - * @param source the player executing the requests - */ - public synchronized void receiveRequests(Request[] requests, Participant source) { - Logger.trace("Received {} requests from participant '{}' of type {}", - requests.length, - source.id.getName(), - source.type); - - refreshTimeoutTimer(source); - - if (activePlayer != source && source.type != ParticipantType.Spectator) { - Logger.trace("Resetting bad requests as new participant sent data"); - activePlayer = source; - badRequests = 0; - } - - Logger.info("got {} requests from participant {}", - requests.length, - source.id.getName()); - - Logger.trace("Processing requests through pipeline"); - Optional> resultingEvents = pipeline.processRequests(requests, source); - Logger.debug("generated {} events from the pipeline", resultingEvents.map(List::size).orElse(0)); - - //resultingEvents isEmpty when a wrong request appeared - Logger.trace("Checking whether resultingEvents (an optional) is empty"); - if (resultingEvents.isEmpty()) { - Logger.debug("Rejecting requests from participant '{}'", source.id.getName()); - reject(source); - } else { - if (game.state.isWon()) { // If game was won in the current turn - Logger.info("Game is won, terminating lobby"); - var events = resultingEvents.get(); - events.add(new EventBuilder(EventType.DisconnectEvent).buildGameEvent()); - accept(source, events); - connection.terminate(); - return; - } else { // If not (normally) - accept(source, resultingEvents.get()); - } - } - updateTurnTimer(); - } - - /** - * Called by {@link LobbyConnection} when a client disconnects - * - * @param source the player disconnecting - */ - public synchronized void handleDisconnect(Participant source) { - - } - - /** - * Called by {@link LobbyConnection} when a client reconnects - * - * @param source the player reconnecting - */ - public synchronized void handleReconnect(Participant source) { - - } - - /** - * This method is called at the end of receiveRequests, to start a timer. The active player has now a specific - * amount of time to do his moves. - */ - void updateTurnTimer() { - var currentlyActiveParticipant = - getParticipantForEntityType(game.state.getActiveCharacter()); - Logger.trace("Updating turnTimer..."); - if (pauseSegment.isPaused()) { - Logger.trace("Game is paused, clearing turnTimer"); - turnTimeoutTimer.clear(); - - } else if (currentlyActiveParticipant.isPresent()) { - var participant = currentlyActiveParticipant.get(); - Logger.trace("Scheduling turnTimer for Player1"); - turnTimeoutTimer.startTurnTimer(participant); - - } else { - Logger.trace("Currently active participant was NPC, clearing TurnTimer"); - turnTimeoutTimer.clear(); - } - } - - /** - * Returns an {@link Optional} of the {@link Participant} for the given {@link EntityType}, or an empty {@link - * Optional} if it was an NPC - */ - Optional getParticipantForEntityType(EntityID entityID) { - if (entityID == null) { - Logger.trace("cannot get participant for empty EntityType, returning empty Optional"); - return Optional.empty(); - } else { - return getParticipantForEntityType(entityID.type); - } - } - - Optional getParticipantForEntityType(EntityType type) { - if (type == EntityType.P1) { - return Optional.ofNullable(connection.getPlayer1()); - } else if (type == EntityType.P2) { - return Optional.ofNullable(connection.getPlayer2()); - } else { - return Optional.empty(); - } - } - - /** Method meant for updating a TurnTimer. Said TurnTimer will be refreshed with the given participant. */ - void refreshTimeoutTimer(Participant participant) { - if (participant.type == ParticipantType.Spectator) { - Logger.trace("Tried to refresh timeoutTimer for Spectator, ignoring..."); - } else { - Logger.debug("Refreshing timeoutTimer for Participant '{}'", participant.id.getName()); - timeoutTimer.refreshTimer(participant); - } - - } - - private void accept(Participant source, List accepted) { - if (!accepted.isEmpty()) { - Logger.debug("Accepting requests from participant '{}', broadcasting events to all except source", - source.id.getName()); - accepted.add(game.getGameStateEvent()); - connection.broadcastToAllExcept(source, accepted.toArray(new Event[0])); - - Logger.trace("Adding ack and sending back to originParticipant"); - accepted.add(0, new EventBuilder(EventType.Ack).buildGameEvent()); - connection.sendEvents(source, accepted.toArray(new Event[0])); - } - badRequests = 0; - } - - /** - * If the player executed a false request the request gets rejected. - * - * @param source the executing player - */ - private void reject(Participant source) { - connection.sendEvents(source, new EventBuilder(EventType.Nack).buildGameEvent(), game.getGameStateEvent()); - badRequests++; - //if the player sends 2 bad messages after one another, the player gets kicked out of the lobby. - if (badRequests >= 5) { - Logger.info("Participant '{}' has sent too many bad requests, disconnecting...", source.id.getName()); - connection.removeParticipant(source); - if (connection.hasPlayer1()) { - Logger.debug("Triggering win for Player 1"); - triggerWin(connection.getPlayer1()); - } else if (connection.hasPlayer2()) { - Logger.debug("Triggering win for Player 2"); - triggerWin(connection.getPlayer2()); - } - } - } - - /** - * Warns the player he get timeouted soon. - * - * @param source soon to be timeouted player - */ - public synchronized void soonTimeout(Participant source, int timeLeft) { - connection.sendEvents( - source, - new EventBuilder(EventType.TimeoutWarningEvent) - .withTimeLeft(timeLeft) - .withMessage("If you do not send a message soon, you will be time-outed") - .buildGameEvent()); - } - - /** - * If a player times out the other player automatically wins. - * - * @param source is the timeouted player - */ - public synchronized void timeout(Participant source) { - connection.sendEvents( - source, - new EventBuilder(EventType.TimeoutEvent).buildGameEvent()); - connection.removeParticipant(source); - - if (connection.hasPlayer1() && !connection.hasPlayer2()) { - triggerWin(connection.getPlayer1()); - } else if (!connection.hasPlayer1() && connection.hasPlayer2()) { - triggerWin(connection.getPlayer2()); - } else { - throw new IllegalStateException("Spectator was time-outed which is impossible"); - } - } - - /** Skips the current turn, and starts a new one. Exclusively called in the {@link TurnTimeoutTimer}. */ - private synchronized void turnTimeout(Participant source) { - var nextTurnEvents = game.endTurn(); - nextTurnEvents.add(game.getGameStateEvent()); - connection.broadcastEvents(nextTurnEvents.toArray(new Event[0])); - updateTurnTimer(); - } - - /** - * The method triggers a winEvent for a {@link Participant}, and broadcasts said event. Afterwards it sends a - * DisconnectRequest to everyone, and terminates the connection. - * - * @param winner is the {@link Participant} that won - */ - public synchronized void triggerWin(Participant winner) { - Logger.debug("Triggering win. Building events and broadcasting for winner of type '{}'", winner); - connection.broadcastEvents( - new EventBuilder(EventType.WinEvent) - .withPlayerWon(winner.type.equals(ParticipantType.PlayerOne) ? 1 : 2) - .buildGameEvent(), - new EventBuilder(EventType.DisconnectEvent) - .buildGameEvent()); - connection.terminate(); - } - - public PauseSegment getPauseSegment() { - return pauseSegment; - } - - public String getGameID() { - return gameID; - } - - public LobbyConnection getConnection() { - return connection; - } - - public GameInstance getGame() { - return game; - } - - public Pipeline getPipeline() { - return pipeline; - } - - public Participant getActivePlayer() { - return activePlayer; - } - - // Note: DO NOT ADD the connection to the equals and hashcode here, otherwise they recursively call each other - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Lobby lobby = (Lobby) o; - return badRequests == lobby.badRequests && Objects.equals(gameID, lobby.gameID) && Objects.equals(game, lobby.game) && Objects.equals(pipeline, lobby.pipeline) && Objects.equals(activePlayer, lobby.activePlayer) && Objects.equals(pauseSegment, lobby.pauseSegment) && Objects.equals(turnTimeoutTimer, lobby.turnTimeoutTimer); - } - - @Override - public int hashCode() { - return Objects.hash(gameID, game, pipeline, activePlayer, badRequests, pauseSegment, turnTimeoutTimer); - } - - @Override - public String toString() { - return "Lobby{" + - "gameID='" + gameID + '\'' + - ", game=" + game + - ", pipeline=" + pipeline + - ", activePlayer=" + activePlayer + - ", badRequests=" + badRequests + - ", pauseSegment=" + pauseSegment + - ", turnTimer=" + turnTimeoutTimer + - '}'; - } -} diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/TimeoutTimer.java b/Server/src/main/java/uulm/teamname/marvelous/server/lobby/TimeoutTimer.java deleted file mode 100644 index 96e62b9..0000000 --- a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/TimeoutTimer.java +++ /dev/null @@ -1,91 +0,0 @@ -package uulm.teamname.marvelous.server.lobby; - -import org.tinylog.Logger; -import uulm.teamname.marvelous.server.lobbymanager.Participant; - -import java.util.concurrent.*; -import java.util.function.BiConsumer; -import java.util.function.Consumer; - -public class TimeoutTimer { - - private final ScheduledExecutorService timer; - - private ScheduledFuture player1AlmostTimeout; - private ScheduledFuture player1Timeout; - private ScheduledFuture player2AlmostTimeout; - private ScheduledFuture player2Timeout; - - private final BiConsumer almostTimeoutCallback; - private final Consumer timeoutCallback; - - private final int almostTimeoutTime; - private final int timeoutTime; - - /** - * Class that manages timeouts of players after not sending a message for a long time. - * - * @param timeoutTime is the time that a player has to send any message - * @param almostTimeoutCallback is the callback that is called if the timeoutTime is almost over - * @param timeoutCallback is the callback that is called when the timeoutTime is over - */ - public TimeoutTimer(int timeoutTime, - BiConsumer almostTimeoutCallback, - Consumer timeoutCallback) { - this.almostTimeoutCallback = almostTimeoutCallback; - this.timeoutCallback = timeoutCallback; - - this.timeoutTime = timeoutTime; - this.almostTimeoutTime = Math.max(timeoutTime - 15, Math.min(timeoutTime, 5)); - - String lobbyThreadName = Thread.currentThread().getName(); - ThreadFactory threadFactory = r -> new Thread(r, lobbyThreadName + "-timeoutTimer"); - timer = Executors.newSingleThreadScheduledExecutor(threadFactory); - } - - /** Refreshes the timeout timer for the given Participant. This is meant to be used for Players, not Spectators. */ - public void refreshTimer(Participant participant) { - Logger.debug("Refreshing turnTimer for participant of type '{}'", participant.type); - switch (participant.type) { - case PlayerOne -> { - Logger.trace("Was playerOne, refreshing..."); - if (player1AlmostTimeout != null) player1AlmostTimeout.cancel(false); - player1AlmostTimeout = - timer.schedule(() -> { - almostTimeoutCallback.accept(participant, timeoutTime - almostTimeoutTime); - return participant; - }, this.almostTimeoutTime, TimeUnit.SECONDS); - - if (player1Timeout != null) player1Timeout.cancel(false); - player1Timeout = - timer.schedule(() -> { - timeoutCallback.accept(participant); - return participant; - }, this.timeoutTime, TimeUnit.SECONDS); - } - case PlayerTwo -> { - Logger.trace("Was playerOne, refreshing..."); - if (player2AlmostTimeout != null) player2AlmostTimeout.cancel(false); - player2AlmostTimeout = - timer.schedule(() -> { - almostTimeoutCallback.accept(participant, timeoutTime - almostTimeoutTime); - return participant; - }, this.almostTimeoutTime, TimeUnit.SECONDS); - - if (player2Timeout != null) player2Timeout.cancel(false); - player2Timeout = - timer.schedule(() -> { - timeoutCallback.accept(participant); - return participant; - }, this.timeoutTime, TimeUnit.SECONDS); - } - case Spectator -> Logger.warn("Timeout called on spectator '{}'. This is probably a bug.", - participant.id.getName()); - } - } -} - - - - - diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/TurnTimeoutTimer.java b/Server/src/main/java/uulm/teamname/marvelous/server/lobby/TurnTimeoutTimer.java deleted file mode 100644 index 39f6f3e..0000000 --- a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/TurnTimeoutTimer.java +++ /dev/null @@ -1,56 +0,0 @@ -package uulm.teamname.marvelous.server.lobby; - -import org.tinylog.Logger; -import uulm.teamname.marvelous.gamelibrary.messages.ParticipantType; -import uulm.teamname.marvelous.server.lobbymanager.Participant; - -import java.util.concurrent.*; -import java.util.function.Consumer; - -/** - * The {@link TurnTimeoutTimer} class is called by the {@link Lobby} to limit the amount of time a player has per round. - */ -public class TurnTimeoutTimer { - private final ScheduledExecutorService timer; - private final Consumer callback; - private final int maxRoundTime; - - private ScheduledFuture current; - - public TurnTimeoutTimer(int maxRoundTime, Consumer callback) { - String lobbyThreadName = Thread.currentThread().getName(); - ThreadFactory threadFactory = r -> new Thread(r, lobbyThreadName + "-TurnTimerThread"); - this.timer = Executors.newSingleThreadScheduledExecutor(threadFactory); - this.maxRoundTime = maxRoundTime; - this.callback = callback; - } - - /** - * This method checks if the participant is not a spectator. Otherwise it won't start a timer. - * - * @param participant the timer is for - */ - public void startTurnTimer(Participant participant) { - if (participant.type == ParticipantType.Spectator) { - throw new IllegalStateException("Spectators don't have TurnTime"); - } - - clear(); - Logger.debug("Starting turn timer for participant '{}' with role {}", - participant.id.getName(), participant.type); - current = timer.schedule(() -> { - callback.accept(participant); - return participant; - }, - maxRoundTime, - TimeUnit.SECONDS); - } - - /** cancels all currently running timers */ - public void clear() { - Logger.trace("Clearing timer"); - if (this.current != null) { - current.cancel(true); - } - } -} diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/pipelining/DisconnectSegment.java b/Server/src/main/java/uulm/teamname/marvelous/server/lobby/pipelining/DisconnectSegment.java deleted file mode 100644 index 483f63f..0000000 --- a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/pipelining/DisconnectSegment.java +++ /dev/null @@ -1,41 +0,0 @@ -package uulm.teamname.marvelous.server.lobby.pipelining; - -import org.tinylog.Logger; -import uulm.teamname.marvelous.gamelibrary.events.Event; -import uulm.teamname.marvelous.gamelibrary.messages.ParticipantType; -import uulm.teamname.marvelous.gamelibrary.requests.RequestType; -import uulm.teamname.marvelous.server.lobby.Lobby; - -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * The {@link DisconnectSegment} handles requests of {@link RequestType} DisconnectRequest. Therefore it removes the - * disconnecting player-connection from the {@link Lobby}. - */ -public class DisconnectSegment implements Segment { - - private final Lobby parent; - - public DisconnectSegment(Lobby parent) { - this.parent = parent; - } - - @Override - public void processRequests(Packet packet, List carrier, AtomicBoolean abort) { - Logger.trace("DisconnectSegment received {} requests.", packet.size()); - if (packet.containsRequestOfType(RequestType.DisconnectRequest)) { - Logger.debug("Player of Type {} sent DisconnectRequest", packet.getOrigin().type); - parent.getConnection().removeParticipant(packet.getOrigin()); - - if (packet.getOrigin().type != ParticipantType.Spectator) { - if (parent.getConnection().hasPlayer1()) { - parent.triggerWin(parent.getConnection().getPlayer1()); - } else if (parent.getConnection().hasPlayer2()) { - parent.triggerWin(parent.getConnection().getPlayer2()); - } - } - packet.clear(); - } - } -} diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/pipelining/FilterEndRoundRequestSegment.java b/Server/src/main/java/uulm/teamname/marvelous/server/lobby/pipelining/FilterEndRoundRequestSegment.java deleted file mode 100644 index 4d0d444..0000000 --- a/Server/src/main/java/uulm/teamname/marvelous/server/lobby/pipelining/FilterEndRoundRequestSegment.java +++ /dev/null @@ -1,54 +0,0 @@ -package uulm.teamname.marvelous.server.lobby.pipelining; - -import org.tinylog.Logger; -import uulm.teamname.marvelous.gamelibrary.entities.EntityID; -import uulm.teamname.marvelous.gamelibrary.entities.EntityType; -import uulm.teamname.marvelous.gamelibrary.events.Event; -import uulm.teamname.marvelous.gamelibrary.gamelogic.GameStateView; -import uulm.teamname.marvelous.gamelibrary.messages.ParticipantType; -import uulm.teamname.marvelous.gamelibrary.requests.RequestType; -import uulm.teamname.marvelous.server.lobbymanager.Participant; -import uulm.teamname.marvelous.server.lobby.Lobby; - -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Supplier; - -/** - * {@link Segment} that checks for an {@link RequestType#EndRoundRequest}, and - * if it exists, checks for the player that has sent it. If the player sending - * this request is not the active player, it flags the message as an error. - */ -public class FilterEndRoundRequestSegment implements Segment { - - private final Supplier getActiveCharacterCallback; - - /** - * Creates a new {@link FilterEndRoundRequestSegment} - * @param getActiveCharacterCallback is a {@link Supplier}<{@link Participant}> - * that supplies the currently active participant - * in a {@link Lobby}. It should normally be bound - * to {@link GameStateView#getActiveCharacter()}, by executing - * {@code new Filter...Segment(game.state::getActiveCharacter)}. - */ - public FilterEndRoundRequestSegment(Supplier getActiveCharacterCallback) { - this.getActiveCharacterCallback = getActiveCharacterCallback; - - } - - @Override - public void processRequests(Packet packet, List carrier, AtomicBoolean abort) { - Logger.trace("FilterEndRoundSegment has received {} requests", packet.size()); - if (packet.containsRequestOfType(RequestType.EndRoundRequest)) { - Logger.trace("Packet contains EndRoundRequest"); - - var active = getActiveCharacterCallback.get().type; - var from = packet.getOrigin().type == ParticipantType.PlayerOne ? EntityType.P1 : EntityType.P2; - - if (packet.containsRequestOfType(RequestType.EndRoundRequest) && active != from) { - Logger.trace("Invalid endRoundRequest. Expected {} but got {}. Aborting...", active, from); - abort.set(true); - } - } - } -} diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/LobbyConnection.java b/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/LobbyConnection.java deleted file mode 100644 index 3ef64a8..0000000 --- a/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/LobbyConnection.java +++ /dev/null @@ -1,326 +0,0 @@ -package uulm.teamname.marvelous.server.lobbymanager; - -import org.tinylog.Logger; -import uulm.teamname.marvelous.gamelibrary.ArrayTools; -import uulm.teamname.marvelous.gamelibrary.Tuple; -import uulm.teamname.marvelous.gamelibrary.config.CharacterProperties; -import uulm.teamname.marvelous.gamelibrary.events.Event; -import uulm.teamname.marvelous.gamelibrary.messages.BasicMessage; -import uulm.teamname.marvelous.gamelibrary.messages.ParticipantType; -import uulm.teamname.marvelous.gamelibrary.messages.server.*; -import uulm.teamname.marvelous.gamelibrary.requests.Request; -import uulm.teamname.marvelous.server.Server; -import uulm.teamname.marvelous.server.lobby.Lobby; -import uulm.teamname.marvelous.server.netconnector.ClientState; -import uulm.teamname.marvelous.server.netconnector.SUID; -import uulm.teamname.marvelous.server.netconnector.UserManager; - -import java.util.*; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; - -public class LobbyConnection implements Runnable { - private static boolean synchronous = false; - - public final String gameID; - public LobbyConnectionState state = LobbyConnectionState.Waiting; - - private Participant player1; - private Participant player2; - - private final Set spectators = new HashSet<>(10); - private final Map> selection = new HashMap<>(2); - public final Map options = new HashMap<>(2); - - private final BlockingQueue> requestQueue = new LinkedBlockingQueue<>(); - - private Lobby lobby; - - /** Creates a new LobbyConnection */ - public LobbyConnection(String gameID) { - this.gameID = gameID; - - Tuple picked = Server.getCharacterConfig().getDisjointSetsOfPropertiesOfSize(12); - this.options.put(ParticipantType.PlayerOne, picked.item1); - this.options.put(ParticipantType.PlayerTwo, picked.item2); - } - - - public boolean setSelection(Participant participant, Integer[] selection) { - this.selection.put(participant.id, ArrayTools.toArrayList(selection)); - return this.selection.size() == 2; - } - - public void addParticipant(Participant participant) { - if (this.state == LobbyConnectionState.Started) { - Logger.trace("Set client state to playing"); - participant.getClient().setState(ClientState.Playing); - participant.state = ParticipantState.Playing; - participant.sendMessage(generateGameStructure(participant.type)); - } - if (participant.type == ParticipantType.Spectator) { - Logger.trace("Adding spectator"); - spectators.add(participant); - } else if (participant.type == ParticipantType.PlayerOne) { - player1 = participant; - } else { - player2 = participant; - } - } - - /** Disconnects a participant from the LobbyConnection */ - public void removeParticipant(Participant participant) { - removeParticipant(participant, "You have been disconnected."); - } - - /** Disconnects a participant from the LobbyConnection */ - public void removeParticipant(Participant participant, String reason) { - if (participant != null) { - Logger.debug("Removing participant '{}' with role {} from lobby", - participant.id.getName(), participant.type); - LobbyManager.getInstance().removeParticipant(participant); - UserManager.getInstance().removeClient(participant.getClient(), reason); - - if (participant.type == ParticipantType.Spectator) { - spectators.remove(participant); - } else if (participant.type == ParticipantType.PlayerOne) { - player1 = null; - } else { - player2 = null; - } - } - } - - /** Returns the next free slot in the lobby as a {@link ParticipantType} */ - public ParticipantType freeSlot() { - if (player1 == null) { - return ParticipantType.PlayerOne; - } else if (player2 == null) { - return ParticipantType.PlayerTwo; - } else { - return ParticipantType.Spectator; - } - } - - /** Returns whether there is a player slot available in the lobby */ - public boolean hasFreePlayerSlot() { - return player1 == null || player2 == null; - } - - - public Participant getPlayer1() { - return player1; - } - - public Participant getPlayer2() { - return player2; - } - - public Set getSpectators() { - return spectators; - } - - public boolean hasPlayer1() { - return player1 != null; - } - - public boolean hasPlayer2() { - return player2 != null; - } - - - public void handleMessage(Participant participant, Request[] requests) { - if(synchronous) { - lobby.receiveRequests(requests, participant); - return; - } - - try { - this.requestQueue.put(Tuple.of(participant, requests)); - } catch (InterruptedException ignored) { - } - } - - /** Handles disconnect of a Participant. Hereby, the participant is made ready for reconnection */ - public void handleDisconnect(Participant participant) { - participant.disconnected = true; - if (state == LobbyConnectionState.Started) { - lobby.handleDisconnect(participant); - } - } - - /** Handles reconnect of a Participant. Hereby, the participant is made ready for reconnection */ - public void handleReconnect(Participant participant) { - participant.disconnected = false; - if (state == LobbyConnectionState.Started) { - - GeneralAssignmentMessage response = new GeneralAssignmentMessage(); - response.gameID = gameID; - participant.sendMessage(response); - - participant.sendMessage(generateGameStructure(participant.type)); - - lobby.handleReconnect(participant); - } - } - - - public void runSynchronous() { - synchronous = true; - run(); - } - - @Override - public void run() { - state = LobbyConnectionState.Started; - - player1.state = ParticipantState.Playing; - player2.state = ParticipantState.Playing; - for (Participant spectator : spectators) { - spectator.state = ParticipantState.Playing; - } - - if(!synchronous) { - Logger.info("Starting Lobby thread for lobby '{}'", gameID); - }else { - Logger.info("Starting Lobby in main thread for lobby '{}'", gameID); - } - - broadcastGameStructure(); - - this.lobby = new Lobby( - gameID, - this, - Server.getPartyConfig(), - Server.getCharacterConfig(), - Server.getScenarioConfig(), - selection.get(player1.id), - selection.get(player2.id) - ); - - if(synchronous) { - return; - } - - while (state == LobbyConnectionState.Started) { - Tuple currentRequests = pollQueueAsync(); - - if (currentRequests != null) { - lobby.receiveRequests(currentRequests.item2, currentRequests.item1); - } - } - } - - public void terminate() { - LobbyRunner.getInstance().removeLobby(this); - LobbyManager.getInstance().removeLobby(gameID); - state = LobbyConnectionState.Aborted; - removeParticipant(player1); - removeParticipant(player2); - spectators.forEach(this::removeParticipant); - } - - - private void broadcastGameStructure() { - // Sending GameStructure message with fitting assignment - player1.sendMessage(generateGameStructure(ParticipantType.PlayerOne)); - player2.sendMessage(generateGameStructure(ParticipantType.PlayerTwo)); - broadcastToSpectators(generateGameStructure(ParticipantType.Spectator)); - } - - private GameStructureMessage generateGameStructure(ParticipantType assignment) { - GameStructureMessage gameStructureMessage = new GameStructureMessage(); - gameStructureMessage.playerOneName = player1.id.getName(); - gameStructureMessage.playerTwoName = player2.id.getName(); - - gameStructureMessage.playerOneCharacters = new CharacterProperties[6]; - gameStructureMessage.playerTwoCharacters = new CharacterProperties[6]; - int i = 0; - for (Integer id : selection.get(player1.id)) { - gameStructureMessage.playerOneCharacters[i++] = Server.getCharacterConfig().getIDMap().get(id); - } - i = 0; - for (Integer id : selection.get(player2.id)) { - gameStructureMessage.playerTwoCharacters[i++] = Server.getCharacterConfig().getIDMap().get(id); - } - - gameStructureMessage.matchconfig = Server.getPartyConfig(); - gameStructureMessage.scenarioconfig = Server.getScenarioConfig(); - - gameStructureMessage.assignment = assignment; - - return gameStructureMessage; - } - - private Tuple pollQueueAsync() { - Tuple current = null; - try { - current = requestQueue.poll(1000, TimeUnit.MILLISECONDS); - } catch (InterruptedException ignored) { - } - return current; - } - - - private void broadcast(BasicMessage message) { - Logger.trace("Broadcasting message of type {} to all members of lobby", message.messageType); - if (player1 != null) player1.sendMessage(message); - if (player2 != null) player2.sendMessage(message); - spectators.forEach(spectator -> spectator.sendMessage(message)); - } - - private void broadcastToSpectators(BasicMessage message) { - Logger.trace("Broadcasting message of type {} to all spectators", message.messageType); - spectators.forEach(spectator -> spectator.sendMessage(message)); - } - - private void broadcastToAllExcept(Participant except, BasicMessage message) { - Logger.trace("Sending message of type {} to all except participant with role {}", - message.messageType, except == null ? "NONE" : except.type); - if (except != null) { - if (!except.equals(player1)) player1.sendMessage(message); - if (!except.equals(player2)) player2.sendMessage(message); - for (Participant spectator : spectators) { - if (!except.equals(spectator)) { - spectator.sendMessage(message); - } - } - } else { - broadcast(message); - } - } - - - public void sendEvents(Participant recipient, Event... events) { - Logger.trace("Sending {} events to participant with role {}", - events.length, recipient == null ? "NONE" : recipient.type); - if (recipient != null) { - EventMessage message = new EventMessage(); - message.messages = events; - - recipient.sendMessage(message); - } - } - - public void broadcastEvents(List events) { - broadcastEvents(events.toArray(new Event[0])); - } - - public void broadcastEvents(Event... events) { - Logger.trace("Broadcasting {} events", events.length); - EventMessage message = new EventMessage(); - message.messages = events; - - broadcast(message); - } - - public void broadcastToAllExcept(Participant except, Event... events) { - Logger.trace("Broadcasting {} events to all except participant with role {}", - events.length, except == null ? "NONE" : except.type); - var message = new EventMessage(); - message.messages = events; - - broadcastToAllExcept(except, message); - } -} diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/LobbyConnectionState.java b/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/LobbyConnectionState.java deleted file mode 100644 index e4ce75c..0000000 --- a/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/LobbyConnectionState.java +++ /dev/null @@ -1,7 +0,0 @@ -package uulm.teamname.marvelous.server.lobbymanager; - -public enum LobbyConnectionState { - Waiting, - Started, - Aborted -} diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/LobbyManager.java b/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/LobbyManager.java deleted file mode 100644 index 824beea..0000000 --- a/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/LobbyManager.java +++ /dev/null @@ -1,273 +0,0 @@ -package uulm.teamname.marvelous.server.lobbymanager; - -import org.tinylog.Logger; -import uulm.teamname.marvelous.gamelibrary.config.CharacterProperties; -import uulm.teamname.marvelous.gamelibrary.messages.ParticipantType; -import uulm.teamname.marvelous.gamelibrary.messages.RoleEnum; -import uulm.teamname.marvelous.gamelibrary.messages.client.CharacterSelectionMessage; -import uulm.teamname.marvelous.gamelibrary.messages.client.PlayerReadyMessage; -import uulm.teamname.marvelous.gamelibrary.messages.client.RequestMessage; -import uulm.teamname.marvelous.gamelibrary.messages.server.ConfirmSelectionMessage; -import uulm.teamname.marvelous.gamelibrary.messages.server.GameAssignmentMessage; -import uulm.teamname.marvelous.gamelibrary.messages.server.GeneralAssignmentMessage; -import uulm.teamname.marvelous.server.netconnector.Client; -import uulm.teamname.marvelous.server.netconnector.ClientState; -import uulm.teamname.marvelous.server.netconnector.SUID; -import uulm.teamname.marvelous.server.netconnector.UserManager; - -import java.util.HashMap; -import java.util.concurrent.atomic.AtomicBoolean; - -public class LobbyManager { - private static LobbyManager instance; - - /** - * @return the current instance of the LobbyManager - */ - public static LobbyManager getInstance() { - if (instance == null) { - instance = new LobbyManager(); - } - return instance; - } - - private final HashMap lobbies = new HashMap<>(); - - private final HashMap participants = new HashMap<>(); - - - /** - * Handles a newly connected Client, and returns whether the game is already running in the given {@link - * AtomicBoolean}. - */ - public boolean handleConnect(Client client, AtomicBoolean running) { - Logger.debug("Connecting new client"); - - Participant participant = participants.get(client.getId()); - if (participant != null) { - LobbyConnection lobby = lobbies.get(participant.lobby); - if (lobby != null) { - running.set(lobby.state == LobbyConnectionState.Started); - } - } - - return true; - } - - public boolean handleReady(Client client, PlayerReadyMessage message) { - if (participants.containsKey(client.getId())) { - return false; - } - - addParticipant(client, client.socket.getResourceDescriptor(), message.role); - - return true; - } - - public boolean handleReconnect(Client client) { - if (!participants.containsKey(client.getId())) { - return false; - } - - Participant participant = participants.get(client.getId()); - participant.setClient(client); - - LobbyConnection lobby = lobbies.get(participant.lobby); - - if (lobby == null) { - return false; - } - - lobby.handleReconnect(participant); - - return true; - } - - /** - * Handles a {@link CharacterSelectionMessage}, computes the characters that have been selected and relays that - * information to the {@link LobbyConnection} concerned by this information. - * - * @param client is the client that sent the message - * @param message is the message sent by the client - * @return true if handled successfully, and false otherwise - */ - public boolean handleSelection(Client client, CharacterSelectionMessage message) { - Logger.debug("Handling characterSelection..."); - if (!participants.containsKey(client.getId())) { - Logger.trace("Participant didn't exist, returning..."); - return false; - } - - Participant participant = participants.get(client.getId()); - - if (participant.state != ParticipantState.Assigned) { - Logger.trace("Participant wasn't assigned, exiting..."); - return false; - } else if (participant.type == ParticipantType.Spectator) { - Logger.trace("Spectator sent message, returning..."); - return false; - } - - LobbyConnection lobby = lobbies.get(participant.lobby); - - if (lobby == null) { - return false; - } - - Integer[] selected = new Integer[6]; - - CharacterProperties[] options = lobby.options.get(participant.type); - - int n = 0; - for (int i = 0; i < 12; i++) { - if (Boolean.TRUE.equals(message.characters[i])) { - selected[n++] = options[i].characterID; - } - } - - if (n != 6) { - return false; - } - - participant.state = ParticipantState.Selected; - - boolean complete = lobby.setSelection(participant, selected); - - if (complete) { - lobby.getPlayer1().getClient().setState(ClientState.Playing); - lobby.getPlayer2().getClient().setState(ClientState.Playing); - lobby.getSpectators().forEach(spectator -> spectator.getClient().setState(ClientState.Playing)); - LobbyRunner.getInstance().startLobby(lobby); - } else { - ConfirmSelectionMessage response = new ConfirmSelectionMessage(); - response.selectionComplete = false; - participant.sendMessage(response); - } - - return true; - } - - /** - * Handles a requestMessage, and relays it to the Lobby if valid - * - * @param client is the client that sent the message - * @param message is the message sent by the client - * @return true if handled successfully, and false otherwise - */ - public boolean handleRequests(Client client, RequestMessage message) { - if (!participants.containsKey(client.getId())) { - return false; - } - - Participant participant = participants.get(client.getId()); - - if (participant.state != ParticipantState.Playing) { - return false; - } - - LobbyConnection lobby = lobbies.get(participant.lobby); - - if (lobby == null) { - return false; - } - - lobby.handleMessage(participant, message.messages); - - return true; - } - - /** - * Handles the disconnect of a WebSocket. - */ - public void handleDisconnect(Client client, boolean byRemote) { - Logger.trace("Handling disconnect of Client"); - - if (!participants.containsKey(client.getId())) { - return; - } - - Participant participant = participants.get(client.getId()); - LobbyConnection lobby = lobbies.get(participant.lobby); - - if (lobby == null) { - return; - } - - if(lobby.state == LobbyConnectionState.Started) { - lobby.handleDisconnect(participant); - - }else { - Logger.debug("Deleting participant after leaving a non-started lobby"); - participants.remove(client.getId()); - - if(lobby.hasFreePlayerSlot()) { - Logger.debug("Destroying lobby after last player left"); - lobby.terminate(); - lobbies.remove(participant.lobby); - } - } - } - - - public void removeLobby(String lobbyID) { - lobbies.remove(lobbyID); - } - - /** - * Adds a participant to a lobby. If the maximum amount of lobbies is already filled, or if the lobby requested - * isn't free, the participant is disconnected. - */ - private void addParticipant(Client client, String lobbyID, RoleEnum role) { - Logger.trace("Adding participant '{}' to the lobby '{}'", client.getId(), lobbyID); - - if (!lobbies.containsKey(lobbyID)) { - if (!LobbyRunner.getInstance().canAddLobby()) { - Logger.info("Rejecting participant '{}' as server is already full", client.getId()); - UserManager.getInstance().removeClient(client, "The server is currently full. Please connect as a spectator instead."); - return; - } - Logger.info("Lobby '{}' didn't exist yet, initializing", lobbyID); - lobbies.put(lobbyID, new LobbyConnection(lobbyID)); - } - - LobbyConnection lobby = lobbies.get(lobbyID); - - if (!lobby.hasFreePlayerSlot() && role != RoleEnum.SPECTATOR) { - Logger.debug("No free player slots available, disconnecting client '{}'", client.getId()); - UserManager.getInstance().removeClient(client, "The lobby your requested is already full. Please connect as a spectator instead."); - return; - } - - ParticipantType type = lobby.freeSlot(); - - Logger.trace("New participant '{}' has the role '{}'", client.getId(), type); - - Participant participant = new Participant(client, lobbyID, type, role == RoleEnum.KI); - participants.put(client.getId(), participant); - - lobby.addParticipant(participant); - - if (type != ParticipantType.Spectator) { - Logger.debug("Sending GameAssignment message to user '{}'", client.getId()); - GameAssignmentMessage response = new GameAssignmentMessage(); - response.gameID = lobby.gameID; - response.characterSelection = lobby.options.get(type); - participant.sendMessage(response); - } else { - Logger.debug("Sending GeneralAssignment message to user '{}'", client.getId()); - GeneralAssignmentMessage response = new GeneralAssignmentMessage(); - response.gameID = lobby.gameID; - participant.sendMessage(response); - } - } - - /** - * Removes a participant from the game entirely. This is done when for example a player gets removed from the - * Lobby because of a timeout. - */ - public void removeParticipant(Participant participant) { - if (participant != null) { - participants.remove(participant.id); - } - } -} diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/LobbyRunner.java b/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/LobbyRunner.java deleted file mode 100644 index 91f15c7..0000000 --- a/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/LobbyRunner.java +++ /dev/null @@ -1,76 +0,0 @@ -package uulm.teamname.marvelous.server.lobbymanager; - -import org.tinylog.Logger; -import uulm.teamname.marvelous.server.Server; - -import java.util.HashMap; - -/** - * Class meant for running lobbies. It manages said lobbies, creates threads for it, and moves it into an executor - */ -public class LobbyRunner { - private static boolean synchronous = false; - - private static LobbyRunner instance; - - static LobbyRunner getInstance() { - if (instance == null) { - instance = new LobbyRunner(); - } - return instance; - } - - private final HashMap activeLobbies = new HashMap<>(); - - - boolean canAddLobby() { - return activeLobbies.size() < Server.getMaxLobbies(); - } - - /** Starts a new thread for the current LobbyConnection, and adds it to the currently active lobbies */ - void startLobby(LobbyConnection connection) { - Logger.trace("Starting lobby connection thread '{}'", connection.gameID); - synchronized (activeLobbies) { - if (activeLobbies.containsKey(connection)) { - Logger.warn("Already active lobby was started again. This is probably a bug."); - } else { - if(!synchronous) { - Logger.trace("Executing LobbyThread 'Lobby-{}'...", connection.gameID); - Thread lobbyThread = new Thread(connection, "Lobby-" + connection.gameID); - activeLobbies.put(connection, lobbyThread); - lobbyThread.start(); - }else { - Logger.trace("Executing Lobby 'Lobby-{}'...", connection.gameID); - connection.runSynchronous(); - } - } - } - } - - void removeLobby(LobbyConnection lobby) { - synchronized (activeLobbies) { - if (!activeLobbies.containsKey(lobby)) { - Logger.warn("Tried to remove non-existent lobby thread. This is probably a bug."); - } else { - Logger.debug("Stopping and removing lobby '{}'", lobby.gameID); - activeLobbies.remove(lobby); - } - } - } - - boolean isStarted(LobbyConnection connection) { - return activeLobbies.containsKey(connection); - } - - /** Shutdown all threads, destroy the lobbies, and close everything up */ - void shutdownAll() { - Logger.info("Stopping and removing all LobbyThreads"); - activeLobbies.keySet().forEach(LobbyConnection::terminate); - Logger.debug("All lobby shutdown flags set"); - } - - // later... - void checkThreads() { - - } -} diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/Participant.java b/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/Participant.java deleted file mode 100644 index f8d7f48..0000000 --- a/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/Participant.java +++ /dev/null @@ -1,62 +0,0 @@ -package uulm.teamname.marvelous.server.lobbymanager; - -import uulm.teamname.marvelous.gamelibrary.messages.BasicMessage; -import uulm.teamname.marvelous.gamelibrary.messages.ParticipantType; -import uulm.teamname.marvelous.server.netconnector.Client; -import uulm.teamname.marvelous.server.netconnector.SUID; - -public class Participant { - private Client client; - public final SUID id; - public final String lobby; - public ParticipantState state = ParticipantState.Assigned; - public final ParticipantType type; - public boolean disconnected = false; - public final boolean isAI; - - public Participant(Client client, String lobby, ParticipantType type, boolean ai) { - this.client = client; - this.id = client.getId(); - this.lobby = lobby; - this.type = type; - this.isAI = ai; - } - - public void setClient(Client client) { - this.client = client; - } - - public Client getClient() { - return client; - } - - public boolean sendError(String error) { - if(disconnected) { - return false; - } - return client.sendError(error); - } - - public boolean sendMessage(BasicMessage message) { - if(disconnected) { - return false; - } - return client.sendMessage(message); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Participant other = (Participant) o; - return other.id.equals(id); - } - - @Override - public int hashCode(){ - final int prime = 31; - int result = 1; - result = prime * result + ((client == null) ? 0 : client.hashCode()) + ((id == null) ? 0 : id.hashCode()); - return result; - } -} diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/ParticipantState.java b/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/ParticipantState.java deleted file mode 100644 index 9ff527c..0000000 --- a/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/ParticipantState.java +++ /dev/null @@ -1,7 +0,0 @@ -package uulm.teamname.marvelous.server.lobbymanager; - -public enum ParticipantState { - Assigned, - Selected, - Playing -} diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/RandomWordGenerator.java b/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/RandomWordGenerator.java deleted file mode 100644 index 71eee4c..0000000 --- a/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/RandomWordGenerator.java +++ /dev/null @@ -1,139 +0,0 @@ -package uulm.teamname.marvelous.server.lobbymanager; - -import java.util.Random; - -public class RandomWordGenerator { - - - private static Random random = new Random(); - - public static String generateTwoWords() { - var firstWord = randomWords[random.nextInt(randomWords.length)]; - var secondWord = randomWords[random.nextInt(randomWords.length)]; - while (firstWord.equals(secondWord)) { - secondWord = randomWords[random.nextInt(randomWords.length)]; - } - - firstWord = firstWord.substring(0, 1).toUpperCase() + firstWord.substring(1).toLowerCase(); - secondWord = secondWord.substring(0, 1).toUpperCase() + secondWord.substring(1).toLowerCase(); - return firstWord + secondWord; - } - - - private static final String[] randomWords = new String[]{ - "wait", - "release", - "river", - "important", - "mark", - "electric", - "defective", - "poke", - "blue", - "beef", - "spring", - "hurt", - "orange", - "happy", - "zealous", - "flowery", - "accurate", - "brake", - "title", - "x-ray", - "festive", - "wrathful", - "scissors", - "peaceful", - "finicky", - "shape", - "soothe", - "head", - "spotted", - "needless", - "time", - "abundant", - "humdrum", - "mouth", - "trot", - "bounce", - "thank", - "avoid", - "shocking", - "minor", - "secret", - "rabbit", - "protect", - "honey", - "business", - "worthless", - "suggest", - "splendid", - "drab", - "safe", - "gigantic", - "arrive", - "drum", - "hate", - "dinosaurs", - "bore", - "tired", - "regret", - "fit", - "potato", - "confuse", - "childlike", - "vein", - "sound", - "attack", - "exchange", - "back", - "check", - "damaged", - "grandmother", - "division", - "groovy", - "throat", - "office", - "pin", - "stare", - "meddle", - "shivering", - "interfere", - "occur", - "hole", - "sugar", - "test", - "blind", - "free", - "perform", - "cherries", - "flavor", - "stupendous", - "purpose", - "extend", - "risk", - "fanatical", - "grubby", - "beg", - "romantic", - "outrageous", - "swift", - "bath", - "room", - "pocket", - "front", - "flower", - "quicksand", - "mark", - "sturdy", - "resolute", - "letters", - "expert", - "hapless", - "bloody", - "blue-eyed", - "hope", - "chew", - }; -} diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/net/Client.java b/Server/src/main/java/uulm/teamname/marvelous/server/net/Client.java new file mode 100644 index 0000000..a1fbbfe --- /dev/null +++ b/Server/src/main/java/uulm/teamname/marvelous/server/net/Client.java @@ -0,0 +1,104 @@ +package uulm.teamname.marvelous.server.net; + +import org.java_websocket.WebSocket; +import org.java_websocket.framing.CloseFrame; +import org.tinylog.Logger; +import uulm.teamname.marvelous.gamelibrary.messages.BasicMessage; +import uulm.teamname.marvelous.gamelibrary.messages.ErrorMessage; +import uulm.teamname.marvelous.gamelibrary.messages.ParticipantType; + +import java.util.Optional; + +@SuppressWarnings("UnusedReturnValue") +public class Client { + private final WebSocket socket; + private ClientState state = ClientState.Blank; + private SUID id = null; + private ParticipantType type = null; + private boolean ai = false; + + public Client(WebSocket socket) { + this.socket = socket; + } + + public boolean sendError(String error) { + ErrorMessage errorMessage = new ErrorMessage(); + errorMessage.message = error; + return sendMessage(errorMessage); + } + + public boolean sendMessage(BasicMessage message) { + if(socket == null) { + return false; + } + + Optional data = MarvelousServer.json.stringify(message); + + if(data.isEmpty()) { + return false; + } + + Logger.debug("Sending message to " + this + ": " + data.get()); //i hate java so much + + try { + socket.send(data.get()); + return true; + }catch (Exception ignored) { + return false; + } + } + + public boolean disconnect() { + if(socket == null) { + return false; + } + + socket.close(CloseFrame.NORMAL); + return true; + } + + public ClientState getState() { + return state; + } + + public void setState(ClientState state) { + this.state = state; + } + + public SUID getID() { + return id; + } + + public void setID(SUID id) { + this.id = id; + } + + public ParticipantType getType() { + return type; + } + + public void setType(ParticipantType type) { + this.type = type; + } + + public boolean isAI() { + return ai; + } + + public void isAI(boolean ai) { + this.ai = ai; + } + + public WebSocket getSocket() { + return socket; + } + + @Override + public String toString() { + if(id != null) { + return id.toString(); + }else { + return "Client@"+Integer.toHexString(hashCode()); + } + } +} diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/netconnector/ClientState.java b/Server/src/main/java/uulm/teamname/marvelous/server/net/ClientState.java similarity index 61% rename from Server/src/main/java/uulm/teamname/marvelous/server/netconnector/ClientState.java rename to Server/src/main/java/uulm/teamname/marvelous/server/net/ClientState.java index cee5962..1aac3bd 100644 --- a/Server/src/main/java/uulm/teamname/marvelous/server/netconnector/ClientState.java +++ b/Server/src/main/java/uulm/teamname/marvelous/server/net/ClientState.java @@ -1,9 +1,10 @@ -package uulm.teamname.marvelous.server.netconnector; +package uulm.teamname.marvelous.server.net; public enum ClientState { Blank, Ready, Assigned, + Selected, Reconnect, Playing } diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/net/MarvelousServer.java b/Server/src/main/java/uulm/teamname/marvelous/server/net/MarvelousServer.java new file mode 100644 index 0000000..d8e99ff --- /dev/null +++ b/Server/src/main/java/uulm/teamname/marvelous/server/net/MarvelousServer.java @@ -0,0 +1,88 @@ +package uulm.teamname.marvelous.server.net; + +import org.java_websocket.WebSocket; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.server.WebSocketServer; +import org.tinylog.Logger; +import uulm.teamname.marvelous.gamelibrary.json.JSON; +import uulm.teamname.marvelous.server.ServerApplication; + +import java.net.InetSocketAddress; +import java.util.HashMap; + +public class MarvelousServer extends WebSocketServer { + public static final JSON json; + + //static initializers are executed the first time the class is referenced + static { + json = new JSON(ServerApplication.getCharacterConfig()); + } + + public MarvelousServer(InetSocketAddress address) { + super(address); + + Thread.currentThread().setName("WebSocketServer"); + } + + /** A map of all connected clients. */ + private final HashMap clients = new HashMap<>(); + + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + Logger.debug("Connected new user"); + + Client client = new Client(conn); + + synchronized(clients) { + clients.put(conn, client); + } + + Logger.trace("Queueing event..."); + ServerApplication.getSession().addEvent(new SocketEvent(SocketEventType.Connect, client)); + } + + @Override + public void onMessage(WebSocket conn, String message) { + Logger.debug("Message received from {}", conn); + + Client client = clients.get(conn); + + if(client == null) { + return; + } + + if(message.length() == 0) { + return; + } + + Logger.trace("Queueing event..."); + ServerApplication.getSession().addEvent(new SocketEvent(SocketEventType.Message, client, message)); + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + Logger.info("Disconnecting client '{}'", conn); + + Client client = clients.get(conn); + if(client == null) { + return; + } + + Logger.trace("Queueing event..."); + ServerApplication.getSession().addEvent(new SocketEvent(SocketEventType.Disconnect, client)); + + synchronized(clients) { + clients.remove(conn); + } + } + + @Override + public void onStart() { + Logger.info("MarvelousServer started on address {}", this.getAddress().toString()); + } + + @Override + public void onError(WebSocket conn, Exception ex) { + Logger.warn("WebSocket-Error occurred: {}", ex.getMessage()); + } +} diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/netconnector/SUID.java b/Server/src/main/java/uulm/teamname/marvelous/server/net/SUID.java similarity index 94% rename from Server/src/main/java/uulm/teamname/marvelous/server/netconnector/SUID.java rename to Server/src/main/java/uulm/teamname/marvelous/server/net/SUID.java index d0d20dd..c416c23 100644 --- a/Server/src/main/java/uulm/teamname/marvelous/server/netconnector/SUID.java +++ b/Server/src/main/java/uulm/teamname/marvelous/server/net/SUID.java @@ -1,4 +1,4 @@ -package uulm.teamname.marvelous.server.netconnector; +package uulm.teamname.marvelous.server.net; import java.util.Objects; diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/net/ServerSession.java b/Server/src/main/java/uulm/teamname/marvelous/server/net/ServerSession.java new file mode 100644 index 0000000..23caa22 --- /dev/null +++ b/Server/src/main/java/uulm/teamname/marvelous/server/net/ServerSession.java @@ -0,0 +1,393 @@ +package uulm.teamname.marvelous.server.net; + +import org.tinylog.Logger; +import uulm.teamname.marvelous.gamelibrary.json.ValidationUtility; +import uulm.teamname.marvelous.gamelibrary.messages.BasicMessage; +import uulm.teamname.marvelous.gamelibrary.messages.ParticipantType; +import uulm.teamname.marvelous.gamelibrary.messages.RoleEnum; +import uulm.teamname.marvelous.gamelibrary.messages.client.*; +import uulm.teamname.marvelous.gamelibrary.messages.server.*; +import uulm.teamname.marvelous.server.ServerApplication; +import uulm.teamname.marvelous.server.game.GameSession; + +import java.util.*; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +public class ServerSession implements Runnable { + /** Set to true when the thread is asked to stop. */ + private volatile boolean stopping = false; + + /** Queue of socket events to handle. */ + private final BlockingQueue events = new LinkedBlockingQueue<>(); + + public void addEvent(SocketEvent event) { + try { + this.events.put(event); + }catch(InterruptedException ignored) { } + } + + @Override + public void run() { + Thread.currentThread().setName("Session"); + + Logger.trace("Started session thread."); + + while(!stopping) { + try { + SocketEvent event = events.poll(1000, TimeUnit.MILLISECONDS); + if(event != null) { + handleSocketEvent(event); + }else { + Thread.onSpinWait(); + } + }catch (InterruptedException ignored) { } + } + + Logger.trace("Stopped session thread."); + } + + /** + * Handles {@link SocketEvent SocketEvents} from the queue. + * @param event The event to handle + */ + private void handleSocketEvent(SocketEvent event) { + switch(event.type) { + case Connect -> onConnect(event.client); + case Message -> onMessage(event.client, event.data); + case Disconnect -> onDisconnect(event.client); + } + } + + /** Requests the session thread to stop. */ + public void stop() { + Logger.trace("Session stop requested."); + + stopping = true; + } + + + public void broadcast(BasicMessage message, SUID... except) { + if(state != SessionState.Running) { + return; + } + + HashSet filter = new HashSet<>(List.of(except)); + + for(Client player: players) { + if(player == null || filter.contains(player.getID())) { + continue; + } + player.sendMessage(message); + } + for(Client spectator: spectators) { + if(filter.contains(spectator.getID())) { + continue; + } + spectator.sendMessage(message); + } + } + + public void reset() { + if(state != SessionState.Running) { + return; + } + + state = SessionState.Pending; + + for(int i = 0; i < 2; i++) { + if(players[i] != null) { + players[i].disconnect(); + players[i] = null; + } + } + for(Client spectator: spectators) { + spectator.disconnect(); + } + spectators.clear(); + + game = null; + } + + + private SessionState state = SessionState.Pending; + + private GameSession game = null; + + private final Client[] players = new Client[2]; + private final ArrayList spectators = new ArrayList<>(); + + public Client[] getPlayers() { + return players; + } + public ArrayList getSpectators() { + return spectators; + } + + + private void onConnect(Client client) { + //do nothing ? + } + + private void onMessage(Client client, String message) { + Logger.trace("Parsing message..."); + Optional parsed = MarvelousServer.json.parse(message); + + if(parsed.isEmpty()) { + Logger.debug("Message couldn't be parsed, sending error..."); + client.sendError("Message could not be parsed."); + return; + } + + BasicMessage data = parsed.get(); + Optional violations = ValidationUtility.validate(data); + + if(violations.isPresent()) { + Logger.debug("The message that client sent had structural violations: '{}'. Sending error...", violations.get()); + client.sendError(violations.get()); + }else { + HandleMessage(client, data); + } + } + + private void onDisconnect(Client client) { + if(client.getID() == null) { + return; + } + + Logger.debug("Client " + client + " disconnected"); + + if(game != null) { + game.handleDisconnect(client); + } + + for(int i = 0; i < 2; i++) { + if(players[i] != null && players[i].getID().equals(client.getID())) { + players[i] = null; + return; + } + } + for(int i = 0; i < spectators.size(); i++) { + if(spectators.get(i).getID().equals(client.getID())) { + spectators.remove(i); + return; + } + } + } + + + private final String errorInvalidMessage = "This message is invalid right now."; + + private void HandleMessage(Client client, BasicMessage data) { + Logger.debug("Received message from " + client + ": " + data); + + Logger.trace("Handling message..."); + + if(data instanceof HelloServerMessage) { + Logger.trace("Message was instanceof HelloServerMessage"); + HelloServerMessage message = (HelloServerMessage) data; + handleHelloServerMessage(client, message); + + } else if(data instanceof ReconnectMessage) { + ReconnectMessage message = (ReconnectMessage) data; + handleReconnectMessage(client, message); + + } else if(data instanceof PlayerReadyMessage) { + Logger.trace("Message was instanceof PlayerReadyMessage"); + PlayerReadyMessage message = (PlayerReadyMessage) data; + handlePlayerReadyMessage(client, message); + + } else if(data instanceof CharacterSelectionMessage) { + Logger.trace("Message was instanceof CharacterSelectionMessage"); + CharacterSelectionMessage message = (CharacterSelectionMessage) data; + handleCharacterSelectionMessage(client, message); + + } else if(data instanceof RequestMessage) { + Logger.trace("Message was instanceof RequestMessage"); + RequestMessage message = (RequestMessage) data; + handleRequestsMessage(client, message); + + } + } + + /** Handles a HelloServerMessage */ + private void handleHelloServerMessage(Client client, HelloServerMessage message) { + if(client.getState() != ClientState.Blank) { + client.sendError(errorInvalidMessage); + return; + } + + SUID id = new SUID(message.name, message.deviceID); + + boolean known = false; + for(int i = 0; i < 2; i++) { + if(players[i] != null && players[i].getID().equals(id)) { + players[i] = client; + known = true; + break; + } + } + for(int i = 0; i < spectators.size(); i++) { + if(known) { + break; + } + if(spectators.get(i).getID().equals(id)) { + spectators.set(i, client); + known = true; + break; + } + } + + if(!known) { + client.setID(id); + } + + boolean running = false; + if(state == SessionState.Running && known) { + client.setState(ClientState.Reconnect); + running = true; + }else { + client.setState(ClientState.Ready); + } + + HelloClientMessage response = new HelloClientMessage(); + response.runningGame = running; + client.sendMessage(response); + } + + /** Handles a reconnectMessage */ + private void handleReconnectMessage(Client client, ReconnectMessage message) { + if(client.getState() != ClientState.Reconnect) { + client.sendError(errorInvalidMessage); + return; + } + + if(message.reconnect.equals(Boolean.TRUE)) { + client.setState(ClientState.Playing); + + game.handleReconnect(client); + }else { + client.setState(ClientState.Ready); + } + } + + /** Handles a PlayerReadyMessage */ + private void handlePlayerReadyMessage(Client client, PlayerReadyMessage message) { + if(client.getState() != ClientState.Ready) { + client.sendError(errorInvalidMessage); + return; + } + + if(message.startGame.equals(Boolean.FALSE)) { + GoodbyeClientMessage response = new GoodbyeClientMessage(); + response.message = "No game requested."; + client.sendMessage(response); + + client.disconnect(); + return; + } + + ParticipantType participant = null; + if(message.role == RoleEnum.SPECTATOR) { + spectators.add(client); + participant = ParticipantType.Spectator; + }else { + for(int i = 0; i < 2; i++) { + if(players[i] == null) { + players[i] = client; + participant = i == 0 ? ParticipantType.PlayerOne : ParticipantType.PlayerTwo; + break; + } + } + } + + if(participant != null) { + if(game == null) { + game = new GameSession(); + } + + client.setType(participant); + client.isAI(message.role == RoleEnum.KI); + client.setState(ClientState.Assigned); + + if(participant != ParticipantType.Spectator) { + GameAssignmentMessage response = new GameAssignmentMessage(); + response.gameID = game.id; + response.characterSelection = game.characterChoices.get(participant); + client.sendMessage(response); + }else { + if(state == SessionState.Running) { + game.addSpectator(client); + } + } + }else { + client.sendError("The game is already full. Please connect as a spectator instead."); + + client.disconnect(); + } + } + + /** Handles a characterSelectionMessage */ + private void handleCharacterSelectionMessage(Client client, CharacterSelectionMessage message) { + if(client.getState() != ClientState.Assigned) { + client.sendError(errorInvalidMessage); + return; + } + + int total = ServerApplication.getCharacterConfig().characters.length; + int received = message.characters.length; + + if(received != total / 2) { + client.sendError("Your character selection size is invalid."); + return; + } + + int selected = 0; + boolean[] selection = new boolean[total / 2]; + for(int i = 0; i < received; i++) { + if(message.characters[i].equals(Boolean.TRUE)) { + selected++; + selection[i] = true; + }else { + selection[i] = false; + } + } + + if(selected != total / 4) { + client.sendError("Your character selection is incomplete."); + return; + } + + client.setState(ClientState.Selected); + + int complete = 0; + for(int i = 0; i < 2; i++) { + if(players[i] != null && players[i].getState() == ClientState.Selected) { + complete++; + } + } + + game.handleSelection(client, selection); + + if(complete <= 1) { + ConfirmSelectionMessage response = new ConfirmSelectionMessage(); + response.selectionComplete = true; + client.sendMessage(response); + }else { + state = SessionState.Running; + + game.start(); + } + } + + /** Handles a RequestMessage */ + private void handleRequestsMessage(Client client, RequestMessage message) { + if(client.getState() != ClientState.Playing) { + client.sendError(errorInvalidMessage); + return; + } + + game.handleRequests(client, message.messages); + } +} diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/net/SessionState.java b/Server/src/main/java/uulm/teamname/marvelous/server/net/SessionState.java new file mode 100644 index 0000000..2a3a11d --- /dev/null +++ b/Server/src/main/java/uulm/teamname/marvelous/server/net/SessionState.java @@ -0,0 +1,6 @@ +package uulm.teamname.marvelous.server.net; + +public enum SessionState { + Pending, + Running +} diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/net/SocketEvent.java b/Server/src/main/java/uulm/teamname/marvelous/server/net/SocketEvent.java new file mode 100644 index 0000000..5067f47 --- /dev/null +++ b/Server/src/main/java/uulm/teamname/marvelous/server/net/SocketEvent.java @@ -0,0 +1,19 @@ +package uulm.teamname.marvelous.server.net; + +public class SocketEvent { + public final SocketEventType type; + public final Client client; + public final String data; + + public SocketEvent(SocketEventType type, Client client, String data) { + this.type = type; + this.client = client; + this.data = data; + } + + public SocketEvent(SocketEventType type, Client client) { + this.type = type; + this.client = client; + this.data = null; + } +} diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/net/SocketEventType.java b/Server/src/main/java/uulm/teamname/marvelous/server/net/SocketEventType.java new file mode 100644 index 0000000..c0031ee --- /dev/null +++ b/Server/src/main/java/uulm/teamname/marvelous/server/net/SocketEventType.java @@ -0,0 +1,7 @@ +package uulm.teamname.marvelous.server.net; + +public enum SocketEventType { + Connect, + Message, + Disconnect +} diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/netconnector/Client.java b/Server/src/main/java/uulm/teamname/marvelous/server/netconnector/Client.java deleted file mode 100644 index 00ca362..0000000 --- a/Server/src/main/java/uulm/teamname/marvelous/server/netconnector/Client.java +++ /dev/null @@ -1,58 +0,0 @@ -package uulm.teamname.marvelous.server.netconnector; - -import org.java_websocket.WebSocket; -import uulm.teamname.marvelous.gamelibrary.messages.BasicMessage; -import uulm.teamname.marvelous.gamelibrary.messages.ErrorMessage; - -import java.util.Optional; - -public class Client { - public final WebSocket socket; - private SUID id; - private ClientState state = ClientState.Blank; - - public Client(WebSocket socket) { - this.socket = socket; - } - - public boolean sendError(String error) { - ErrorMessage errorMessage = new ErrorMessage(); - errorMessage.message = error; - return sendMessage(errorMessage); - } - - public boolean sendMessage(BasicMessage message) { - if(socket == null) { - return false; - } - - Optional data = UserManager.getInstance().json.stringify(message); - - if (data.isEmpty()) { - return false; - } - - socket.send(data.get()); - return true; - } - - public WebSocket getSocket() { - return socket; - } - - public ClientState getState() { - return state; - } - - public SUID getId() { - return id; - } - - public void setId(SUID id) { - this.id = id; - } - - public void setState(ClientState state) { - this.state = state; - } -} diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/netconnector/MarvelousServer.java b/Server/src/main/java/uulm/teamname/marvelous/server/netconnector/MarvelousServer.java deleted file mode 100644 index 574da86..0000000 --- a/Server/src/main/java/uulm/teamname/marvelous/server/netconnector/MarvelousServer.java +++ /dev/null @@ -1,42 +0,0 @@ -package uulm.teamname.marvelous.server.netconnector; - -import org.java_websocket.WebSocket; -import org.java_websocket.handshake.ClientHandshake; -import org.java_websocket.server.WebSocketServer; -import org.tinylog.Logger; - -import java.net.InetSocketAddress; - -public class MarvelousServer extends WebSocketServer { - @Override - public void onOpen(WebSocket conn, ClientHandshake handshake) { - Logger.info("New client connected."); - UserManager.getInstance().connectUser(conn); - } - - @Override - public void onClose(WebSocket conn, int code, String reason, boolean remote) { - Logger.info("Client disconnected."); - UserManager.getInstance().disconnectUser(conn, remote); - } - - @Override - public void onMessage(WebSocket conn, String message) { - Logger.debug("Message received: {}", message); - UserManager.getInstance().messageReceived(conn, message); - } - - @Override - public void onError(WebSocket conn, Exception ex) { - Logger.warn("WebSocket-Error occurred: {}", ex.getMessage()); - } - - @Override - public void onStart() { - Logger.info("MarvelousServer started on Address {}", this.getAddress().toString()); - } - - public MarvelousServer(InetSocketAddress address) { - super(address); - } -} diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/netconnector/UserManager.java b/Server/src/main/java/uulm/teamname/marvelous/server/netconnector/UserManager.java deleted file mode 100644 index ada923e..0000000 --- a/Server/src/main/java/uulm/teamname/marvelous/server/netconnector/UserManager.java +++ /dev/null @@ -1,278 +0,0 @@ -package uulm.teamname.marvelous.server.netconnector; - -import org.java_websocket.framing.CloseFrame; -import org.tinylog.Logger; -import uulm.teamname.marvelous.gamelibrary.json.JSON; -import uulm.teamname.marvelous.gamelibrary.json.ValidationUtility; -import uulm.teamname.marvelous.gamelibrary.messages.BasicMessage; -import uulm.teamname.marvelous.gamelibrary.messages.client.*; -import uulm.teamname.marvelous.gamelibrary.messages.server.GoodbyeClientMessage; -import uulm.teamname.marvelous.gamelibrary.messages.server.HelloClientMessage; -import uulm.teamname.marvelous.server.Server; -import uulm.teamname.marvelous.server.lobbymanager.LobbyManager; - -import org.java_websocket.WebSocket; - -import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * Class that manages users. It is meant as an extension to the {@link MarvelousServer} class. This is the first place - * where messages are relayed to after they get received. This class is responsible for handshakes, reconnects and - * WebSocket-to-Participant matching. It is designed to be thread-safe. - */ -public class UserManager { - private static UserManager instance; - private static final String errorInvalidMessage = "Invalid message."; - - /** - * @return the current instance of the UserManager - */ - public static UserManager getInstance() { - if (instance == null) { - instance = new UserManager(); - } - return instance; - } - - /** A map of all connected clients. */ - private final HashMap clients = new HashMap<>(); - - public final JSON json; - - /** Constructs a new, empty UserManager */ - private UserManager() { - this.json = new JSON(Server.getCharacterConfig()); - } - - /** Called on a new WebSocket connection. */ - public void connectUser(WebSocket conn) { - Logger.debug("Connected new user"); - synchronized(clients) { - clients.put(conn, new Client(conn)); - } - } - - /** - * Called on any received messages. The method checks the message for validity, and then relays it accordingly. - * @param conn is the {@link WebSocket} that sent the message - * @param message is the {@link String} sent by the connection - */ - public void messageReceived(WebSocket conn, String message) { - Logger.debug("Message received from {}", conn); - Client client = clients.get(conn); - - if(client == null) { - Logger.warn("messageReceived called with null-valued client. This is probably a bug."); - return; - } - - Logger.trace("Parsing message..."); - Optional parsed = json.parse(message); - - if(parsed.isEmpty()) { - Logger.debug("Message couldn't be parsed, sending error..."); - client.sendError("Message could not be parsed."); - return; - } - - BasicMessage data = parsed.get(); - Optional violations = ValidationUtility.validate(data); - - if(violations.isPresent()) { - Logger.debug("The message that client sent had structural violations: '{}'. Sending error...", - violations.get()); - client.sendError(violations.get()); - return; - } - - Logger.trace("Handling message..."); - handleMessage(client, data); - } - - /** Called on closed connection. */ - public void disconnectUser(WebSocket conn, boolean closedByRemote) { - Logger.info("Disconnecting client '{}'", conn); - Client client = clients.get(conn); - - if(client == null) { - Logger.warn("disconnect called with null-valued client. This is probably a bug."); - return; - } - - Logger.trace("Calling handleDisconnect on LobbyManager"); - LobbyManager.getInstance().handleDisconnect(client, closedByRemote); - - synchronized(clients) { - Logger.debug("Removing connections from clients HashMap"); - clients.remove(conn); - } - } - - - /** Checks type of a given message, and delegates its execution to the respective handler method */ - private void handleMessage(Client client, BasicMessage data) { - Logger.debug("Received message from client '{}'", client); - if(data instanceof HelloServerMessage) { - Logger.trace("Message was instanceof HelloServerMessage"); - HelloServerMessage message = (HelloServerMessage) data; - handleHelloServerMessage(client, message); - - } else if(data instanceof ReconnectMessage) { - ReconnectMessage message = (ReconnectMessage) data; - handleReconnectMessage(client, message); - - } else if(data instanceof PlayerReadyMessage) { - Logger.trace("Message was instanceof PlayerReadyMessage"); - PlayerReadyMessage message = (PlayerReadyMessage) data; - handlePlayerReadyMessage(client, message); - - } else if(data instanceof CharacterSelectionMessage) { - Logger.trace("Message was instanceof CharacterSelectionMessage"); - CharacterSelectionMessage message = (CharacterSelectionMessage) data; - handleCharacterSelectionMessage(client, message); - - } else if(data instanceof RequestMessage) { - Logger.trace("Message was instanceof RequestMessage"); - RequestMessage message = (RequestMessage) data; - handleRequestsMessage(client, message); - - } - } - - /** Handles a HelloServerMessage */ - private void handleHelloServerMessage(Client client, HelloServerMessage message) { - if(client.getState() != ClientState.Blank) { - Logger.debug("Disconnecting client as ClientState isn't Blank but {}", client.getState()); - client.sendError("Invalid message, as handshake already completed."); - return; - } - - client.setId(new SUID(message.name, message.deviceID)); - - Logger.trace("forwarding message to the LobbyManager"); - AtomicBoolean running = new AtomicBoolean(false); - if(LobbyManager.getInstance().handleConnect(client, running)) { - - var clientHasRunningGame = running.get(); - if (clientHasRunningGame) { - client.setState(ClientState.Reconnect); - } else { - client.setState(ClientState.Ready); - } - - HelloClientMessage response = new HelloClientMessage(); - response.runningGame = running.get(); - client.sendMessage(response); - } else { - client.sendError("Message could not be processed."); - } - } - - /** Handles a reconnectMessage, and reconnects the client if needed */ - private void handleReconnectMessage(Client client, ReconnectMessage message) { - if(client.getState() != ClientState.Reconnect) { - client.sendError("Invalid message, as client is not in reconnect-ready state"); - return; - } - - if(Boolean.TRUE.equals(message.reconnect)) { - Logger.trace("Reconnecting to lobby. Forwarding reconnect instruction to the LobbyManager"); - if(LobbyManager.getInstance().handleReconnect(client)) { - Logger.trace("Successfully reconnected client, changing state to Playing..."); - client.setState(ClientState.Playing); - } else { - Logger.debug("The client couldn't be reconnected properly"); - client.sendError("You could not be reconnected to the Lobby"); - } - } else { - Logger.trace("No reconnect requested, setting client to ready to connect state"); - client.setState(ClientState.Ready); - } - } - - /** Handles a PlayerReadyMessage, and connects a client to a lobby if valid */ - private void handlePlayerReadyMessage(Client client, PlayerReadyMessage message) { - Logger.trace("Handing PlayerReadyMessage..."); - if(client.getState() != ClientState.Ready) { - Logger.debug("Client wasn't in Ready state but instead in {} state, sending error...", client.getState()); - client.sendError("Invalid message, as client is not in Ready state"); - return; - } - - Logger.trace("Relaying message to LobbyManager"); - if(Boolean.TRUE.equals(message.startGame)) { - if(LobbyManager.getInstance().handleReady(client, message)) { - client.setState(ClientState.Assigned); - } else { - Logger.trace("Sending error to client as message couldn't be processed properly"); - client.sendError(errorInvalidMessage); - } - } else { - Logger.debug("Disconnecting client as game couldn't be started"); - removeClient(client, "You got disconnected."); - } - } - - /** Handles a characterSelectionMessage, and forwards it to the LobbyManager if valid */ - private void handleCharacterSelectionMessage(Client client, CharacterSelectionMessage message) { - Logger.trace("Handling CharacterSelectionMessage"); - if(client.getState() != ClientState.Assigned) { - Logger.debug("Couldn't handle CharacterSelectionMessage as client wasn't in assignedState but in {}", - client.getState()); - client.sendError("Cannot select character, as client is not in the Character Selection phase"); - return; - } - - Logger.trace("relaying message to be handled by the LobbyManager..."); - if(LobbyManager.getInstance().handleSelection(client, message)) { - Logger.trace("Handled successfully"); - } else { - client.sendError(errorInvalidMessage); - } - } - - /** Handles a RequestMessage, and forwards it to the LobbyManager if valid */ - private void handleRequestsMessage(Client client, RequestMessage message) { - Logger.trace("Handling RequestMessage"); - if(client.getState() != ClientState.Playing) { - Logger.debug("Couldn't handle RequestMessage as client wasn't in playingState but in {}", - client.getState()); - client.sendError("Invalid message, as client is not ingame"); - return; - } - - Logger.trace("relaying message to be handled by the LobbyManager..."); - if(LobbyManager.getInstance().handleRequests(client, message)) { - Logger.trace("Handled successfully"); - //"i approve" - the server - } else { - Logger.debug("Message couldn't be handled, sending error to client"); - client.sendError(errorInvalidMessage); - } - } - - - /** - * Called when a client should be removed from the game. - * @param client is the client to be removed - * @param message is the message that is sent to the client in the accompanying {@link GoodbyeClientMessage} - */ - public void removeClient(Client client, String message) { - GoodbyeClientMessage response = new GoodbyeClientMessage(); - response.message = message; - - client.sendMessage(response); - - client.socket.close(CloseFrame.NORMAL); - } - - - public int getUserCount() { - return clients.size(); - } - - public boolean containsConnection(WebSocket conn) { - return clients.containsKey(conn); - } -} diff --git a/Server/src/test/java/uulm/teamname/marvelous/server/BaseGameLogicTest.java b/Server/src/test/java/uulm/teamname/marvelous/server/BaseGameLogicTest.java index 1d3084e..483fb91 100644 --- a/Server/src/test/java/uulm/teamname/marvelous/server/BaseGameLogicTest.java +++ b/Server/src/test/java/uulm/teamname/marvelous/server/BaseGameLogicTest.java @@ -1,9 +1,7 @@ package uulm.teamname.marvelous.server; import uulm.teamname.marvelous.gamelibrary.config.*; -import uulm.teamname.marvelous.server.lobbymanager.LobbyRunner; -import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Iterator; import java.util.concurrent.ThreadLocalRandom; @@ -86,16 +84,4 @@ public class BaseGameLogicTest { return props; } - - static void setPrivateFinalBoolean(Class c, String name, boolean value) throws NoSuchFieldException, IllegalAccessException { - Field field = LobbyRunner.class.getDeclaredField(name); - field.setAccessible(true); - - //this is broken somehow - //Field modifiersField = Field.class.getDeclaredField("modifiers"); - //modifiersField.setAccessible(true); - //modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); - - field.set(null, value); - } } diff --git a/Server/src/test/java/uulm/teamname/marvelous/server/MarvelousServerTest.java b/Server/src/test/java/uulm/teamname/marvelous/server/MarvelousServerApplicationTest.java similarity index 64% rename from Server/src/test/java/uulm/teamname/marvelous/server/MarvelousServerTest.java rename to Server/src/test/java/uulm/teamname/marvelous/server/MarvelousServerApplicationTest.java index 68faede..0edab63 100644 --- a/Server/src/test/java/uulm/teamname/marvelous/server/MarvelousServerTest.java +++ b/Server/src/test/java/uulm/teamname/marvelous/server/MarvelousServerApplicationTest.java @@ -1,6 +1,5 @@ package uulm.teamname.marvelous.server; -import org.java_websocket.WebSocket; import org.junit.jupiter.api.*; import org.mockito.MockedStatic; import org.mockito.Mockito; @@ -15,87 +14,71 @@ import uulm.teamname.marvelous.gamelibrary.messages.client.HelloServerMessage; import uulm.teamname.marvelous.gamelibrary.messages.client.PlayerReadyMessage; import uulm.teamname.marvelous.gamelibrary.messages.client.ReconnectMessage; import uulm.teamname.marvelous.gamelibrary.messages.server.*; -import uulm.teamname.marvelous.server.lobbymanager.LobbyConnection; -import uulm.teamname.marvelous.server.lobbymanager.LobbyManager; -import uulm.teamname.marvelous.server.lobbymanager.LobbyRunner; -import uulm.teamname.marvelous.server.netconnector.UserManager; +import uulm.teamname.marvelous.server.net.Client; +import uulm.teamname.marvelous.server.net.ServerSession; +import uulm.teamname.marvelous.server.net.SocketEvent; +import uulm.teamname.marvelous.server.net.SocketEventType; -import java.lang.reflect.Constructor; import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.util.HashMap; -import java.util.Map; import java.util.Optional; import static org.mockito.Mockito.*; -class MarvelousServerTest extends BaseGameLogicTest { - private static MockedStatic serverMock; +@SuppressWarnings({"ResultOfMethodCallIgnored", "SameParameterValue", "unchecked"}) +class MarvelousServerApplicationTest extends BaseGameLogicTest { + private static ServerSession session; + private static MockedStatic serverMock; private static JSON json; @BeforeAll - static void start() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException { - Constructor um = UserManager.class.getDeclaredConstructor(); - um.setAccessible(true); - um.newInstance(); - Constructor lm = LobbyManager.class.getDeclaredConstructor(); - lm.setAccessible(true); - lm.newInstance(); + static void start() { + session = new ServerSession(); - serverMock = Mockito.mockStatic(Server.class); + serverMock = Mockito.mockStatic(ServerApplication.class); generate(); - setPrivateFinalBoolean(LobbyConnection.class, "synchronous", true); - setPrivateFinalBoolean(LobbyRunner.class, "synchronous", true); - - serverMock.when(Server::getMaxLobbies).thenReturn(10); - serverMock.when(Server::getPartyConfig).thenReturn(partyConfig); - serverMock.when(Server::getScenarioConfig).thenReturn(scenarioConfig); - serverMock.when(Server::getCharacterConfig).thenReturn(characterConfig); + serverMock.when(ServerApplication::getSession).thenReturn(session); + serverMock.when(ServerApplication::getPartyConfig).thenReturn(partyConfig); + serverMock.when(ServerApplication::getScenarioConfig).thenReturn(scenarioConfig); + serverMock.when(ServerApplication::getCharacterConfig).thenReturn(characterConfig); json = new JSON(characterConfig); - if (!Configuration.isFrozen()) { + if(!Configuration.isFrozen()) { Configuration.set("writer1", "console"); Configuration.set("writer1.level", "trace"); Configuration.set("writer1.format", "[{thread}] {level}: {message}"); } } - @Test void main() { - UserManager m = UserManager.getInstance(); + session.run(); - WebSocket p1 = mock(WebSocket.class); - WebSocket p2 = mock(WebSocket.class); - WebSocket s1 = mock(WebSocket.class); - WebSocket s2 = mock(WebSocket.class); + Client p1 = mock(Client.class); + Client p2 = mock(Client.class); + Client s1 = mock(Client.class); + Client s2 = mock(Client.class); - when(p1.getResourceDescriptor()).thenReturn("/"); - when(p2.getResourceDescriptor()).thenReturn("/"); - when(s1.getResourceDescriptor()).thenReturn("/"); - when(s2.getResourceDescriptor()).thenReturn("/"); + session.addEvent(new SocketEvent(SocketEventType.Connect, p1)); + ensureHandshake(p1, "Player 1", "1234", false); - m.connectUser(p1); - ensureHandshake(m, p1, "Player 1", "1234", false); + session.addEvent(new SocketEvent(SocketEventType.Connect, p2)); + ensureHandshake(p2, "Player 2", "4321", false); - m.connectUser(p2); - ensureHandshake(m, p2, "Player 2", "4321", false); + session.addEvent(new SocketEvent(SocketEventType.Connect, s1)); + ensureHandshake(s1, "Spectator 1", "3333", false); - m.connectUser(s1); - ensureHandshake(m, s1, "Spectator 1", "3333", false); + session.addEvent(new SocketEvent(SocketEventType.Connect, s2)); + ensureHandshake(s2, "Spectator 2", "4444", false); - m.connectUser(s2); - ensureHandshake(m, s2, "Spectator 2", "4444", false); + ensurePlayerReady(p1, true, RoleEnum.PLAYER); + ensurePlayerReady(p2, true, RoleEnum.PLAYER); + ensureSpectatorReady(s1, true, RoleEnum.SPECTATOR); - ensurePlayerReady(m, p1, true, RoleEnum.PLAYER); - ensurePlayerReady(m, p2, true, RoleEnum.PLAYER); - ensureSpectatorReady(m, s1, true, RoleEnum.SPECTATOR); - - ensureCharacterSelection(m, p1, true); - ensureCharacterSelection(m, p2, false); + ensureCharacterSelection(p1, true); + ensureCharacterSelection(p2, false); GameStructureMessage game = new GameStructureMessage(); game.playerOneName = "Player 1"; @@ -119,14 +102,14 @@ class MarvelousServerTest extends BaseGameLogicTest { clearInvocations(p1, p2, s1); - m.disconnectUser(p1, true); + session.addEvent(new SocketEvent(SocketEventType.Disconnect, p1)); - m.connectUser(p1); - ensureHandshake(m, p1, "Player 1", "1234", true); + session.addEvent(new SocketEvent(SocketEventType.Connect, p1)); + ensureHandshake(p1, "Player 1", "1234", true); ReconnectMessage message = new ReconnectMessage(); message.reconnect = true; - sendMessage(m, p1, message); + sendMessage(p1, message); ensureReceived(p1, new GeneralAssignmentMessage()); ensureReceived(p1, new GameStructureMessage()); @@ -134,7 +117,7 @@ class MarvelousServerTest extends BaseGameLogicTest { System.out.println("Test Completed"); } - private void ensureHandshake(UserManager m, WebSocket c, String name, String deviceID, boolean runningGame) { + private void ensureHandshake(Client c, String name, String deviceID, boolean runningGame) { HelloServerMessage message = new HelloServerMessage(); message.name = name; message.deviceID = deviceID; @@ -142,10 +125,10 @@ class MarvelousServerTest extends BaseGameLogicTest { HelloClientMessage response = new HelloClientMessage(); response.runningGame = runningGame; - ensureResponse(m, c, message, response); + ensureResponse(c, message, response); } - private void ensurePlayerReady(UserManager m, WebSocket c, boolean startGame, RoleEnum role) { + private void ensurePlayerReady(Client c, boolean startGame, RoleEnum role) { PlayerReadyMessage message = new PlayerReadyMessage(); message.startGame = startGame; message.role = role; @@ -153,10 +136,10 @@ class MarvelousServerTest extends BaseGameLogicTest { GameAssignmentMessage response = new GameAssignmentMessage(); //properties are left null because we can't test their content (they are random) - ensureResponse(m, c, message, response); + ensureResponse(c, message, response); } - private void ensureSpectatorReady(UserManager m, WebSocket c, boolean startGame, RoleEnum role) { + private void ensureSpectatorReady(Client c, boolean startGame, RoleEnum role) { PlayerReadyMessage message = new PlayerReadyMessage(); message.startGame = startGame; message.role = role; @@ -164,10 +147,10 @@ class MarvelousServerTest extends BaseGameLogicTest { GeneralAssignmentMessage response = new GeneralAssignmentMessage(); //properties are left null because we can't test their content (they are random) - ensureResponse(m, c, message, response); + ensureResponse(c, message, response); } - private void ensureCharacterSelection(UserManager m, WebSocket c, boolean confirmSelection) { + private void ensureCharacterSelection(Client c, boolean confirmSelection) { CharacterSelectionMessage message = new CharacterSelectionMessage(); message.characters = new Boolean[12]; for(int i = 0; i < 6; i++) { @@ -180,44 +163,44 @@ class MarvelousServerTest extends BaseGameLogicTest { if(confirmSelection) { ConfirmSelectionMessage response = new ConfirmSelectionMessage(); response.selectionComplete = false; - ensureResponse(m, c, message, response); + ensureResponse(c, message, response); } else { - sendMessage(m, c, message); + sendMessage(c, message); } } /** Sends a message from the socket. */ - private void sendMessage(UserManager m, WebSocket c, BasicMessage message) { + private void sendMessage(Client c, BasicMessage message) { Optional in = json.stringify(message); if(in.isPresent()) { - m.messageReceived(c, in.get()); + session.addEvent(new SocketEvent(SocketEventType.Message, c, in.get())); }else { throw new IllegalArgumentException("[TEST] Message in test call could not be serialized!"); } } /** Ensures that the given socket received the given response in response to the given message. */ - private void ensureResponse(UserManager m, WebSocket c, BasicMessage message, BasicMessage response) { + private void ensureResponse(Client c, BasicMessage message, BasicMessage response) { Optional in = json.stringify(message); if(in.isPresent()) { - m.messageReceived(c, in.get()); - verify(c).send((String)argThat( + session.addEvent(new SocketEvent(SocketEventType.Message, c, in.get())); + verify(c.getSocket()).send((String)argThat( (out)->verifyResponse((String)out, response) )); - clearInvocations(c); + clearInvocations(c.getSocket()); }else { throw new IllegalArgumentException("[TEST] Message in test call could not be serialized!"); } } /** Ensures that the given socket received the given response. */ - private void ensureReceived(WebSocket c, T response) { + private void ensureReceived(Client c, T response) { boolean found = false; - for(Invocation i: mockingDetails(c).getInvocations()) { + for(Invocation i: mockingDetails(c.getSocket()).getInvocations()) { Optional received = json.parse(i.getArgument(0, String.class)); if(received.isPresent()) { @@ -227,7 +210,7 @@ class MarvelousServerTest extends BaseGameLogicTest { continue; //message is not of the right type } - T message = (T) parsed; + T message = (T)parsed; for(Field field: response.getClass().getDeclaredFields()) { try { @@ -265,7 +248,7 @@ class MarvelousServerTest extends BaseGameLogicTest { Optional in = json.parse(actual); if(in.isPresent()) { - T message = (T) in.get(); + T message = (T)in.get(); for(Field field: expected.getClass().getDeclaredFields()) { try { @@ -290,6 +273,7 @@ class MarvelousServerTest extends BaseGameLogicTest { @AfterAll static void stop() { + session.stop(); serverMock.close(); } } diff --git a/Server/src/test/java/uulm/teamname/marvelous/server/lobby/TurnTimeoutTimerTest.java b/Server/src/test/java/uulm/teamname/marvelous/server/lobby/TurnTimeoutTimerTest.java deleted file mode 100644 index d5db765..0000000 --- a/Server/src/test/java/uulm/teamname/marvelous/server/lobby/TurnTimeoutTimerTest.java +++ /dev/null @@ -1,31 +0,0 @@ -package uulm.teamname.marvelous.server.lobby; - -import org.java_websocket.WebSocket; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import uulm.teamname.marvelous.gamelibrary.messages.ParticipantType; -import uulm.teamname.marvelous.server.lobbymanager.Participant; -import uulm.teamname.marvelous.server.netconnector.Client; - -import java.util.function.Consumer; - -import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; - -class TurnTimeoutTimerTest { - TurnTimeoutTimer turnTimeoutTimer; - - @BeforeEach - void beforeEach(){ - var callback = mock(Consumer.class); - turnTimeoutTimer = new TurnTimeoutTimer(20, callback); - } - - @Test - void startTurnTimerTest(){ - var connection = mock(WebSocket.class); - var participant = new Participant(new Client(connection), "lobby", ParticipantType.Spectator, false); - assertThatIllegalStateException().describedAs("Spectators don't have TurnTime").isThrownBy(() -> turnTimeoutTimer.startTurnTimer(participant)); - } - -} diff --git a/Server/src/test/java/uulm/teamname/marvelous/server/lobby/pipelining/DisconnectSegmentTest.java b/Server/src/test/java/uulm/teamname/marvelous/server/lobby/pipelining/DisconnectSegmentTest.java deleted file mode 100644 index de8f272..0000000 --- a/Server/src/test/java/uulm/teamname/marvelous/server/lobby/pipelining/DisconnectSegmentTest.java +++ /dev/null @@ -1,103 +0,0 @@ -package uulm.teamname.marvelous.server.lobby.pipelining; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import uulm.teamname.marvelous.gamelibrary.config.FieldType; -import uulm.teamname.marvelous.gamelibrary.config.ScenarioConfig; -import uulm.teamname.marvelous.gamelibrary.requests.Request; -import uulm.teamname.marvelous.gamelibrary.requests.RequestBuilder; -import uulm.teamname.marvelous.gamelibrary.requests.RequestType; -import uulm.teamname.marvelous.server.lobby.Lobby; -import uulm.teamname.marvelous.server.lobbymanager.LobbyConnection; -import uulm.teamname.marvelous.server.lobbymanager.Participant; - -import java.util.ArrayList; -import java.util.concurrent.atomic.AtomicBoolean; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; -import static org.assertj.core.api.Assertions.*; - -class DisconnectSegmentTest { - Participant player1; - Participant player2; - Participant spectator; - DisconnectSegment disconnectSegment; - Lobby lobby; - LobbyConnection connection; - - @BeforeEach - void beforeEach(){ - connection = mock(LobbyConnection.class); - - player1 = mock(Participant.class); - player2 = mock(Participant.class); - spectator = mock(Participant.class); - - lobby = mock(Lobby.class); - disconnectSegment = new DisconnectSegment(lobby); - - when(lobby.getConnection()).thenReturn(connection); - when(connection.getPlayer1()).thenReturn(player1); - when(connection.getPlayer2()).thenReturn(player2); - } - - @Test - void noDisconnectRequestTest(){ - var requests = new Request[] { - new RequestBuilder(RequestType.Req).buildGameRequest(), - new RequestBuilder(RequestType.MoveRequest).buildGameRequest() - }; - Packet packet = new Packet(requests, player1); - AtomicBoolean abort = new AtomicBoolean(false); - disconnectSegment.processRequests(packet, new ArrayList<>(), abort); - assertThat(packet).containsOnly(requests); - } - - @Test - void disconnectRequestBySpectatorTest(){ - var requests = new Request[] { - new RequestBuilder(RequestType.DisconnectRequest).buildGameRequest(), - new RequestBuilder(RequestType.Req).buildGameRequest(), - new RequestBuilder(RequestType.MoveRequest).buildGameRequest() - }; - Packet packet = new Packet(requests, spectator); - AtomicBoolean abort = new AtomicBoolean(false); - disconnectSegment.processRequests(packet, new ArrayList<>(), abort); - assertThat(packet).doesNotContain(requests); - verify(connection).hasPlayer1(); - verify(connection).hasPlayer2(); - } - - @Test - void disconnectRequestByPlayer1Test(){ - var requests = new Request[] { - new RequestBuilder(RequestType.DisconnectRequest).buildGameRequest(), - new RequestBuilder(RequestType.Req).buildGameRequest(), - new RequestBuilder(RequestType.MoveRequest).buildGameRequest() - }; - Packet packet = new Packet(requests, player1); - AtomicBoolean abort = new AtomicBoolean(false); - disconnectSegment.processRequests(packet, new ArrayList<>(), abort); - assertThat(packet).doesNotContain(requests); - verify(connection).removeParticipant(player1); - assertThat(verify(connection).hasPlayer1()).isFalse(); - verify(connection).hasPlayer2(); - } - - @Test - void disconnectRequestByPlayer2Test(){ - var requests = new Request[] { - new RequestBuilder(RequestType.DisconnectRequest).buildGameRequest(), - new RequestBuilder(RequestType.Req).buildGameRequest(), - new RequestBuilder(RequestType.MoveRequest).buildGameRequest() - }; - Packet packet = new Packet(requests, player2); - AtomicBoolean abort = new AtomicBoolean(false); - disconnectSegment.processRequests(packet, new ArrayList<>(), abort); - assertThat(packet).doesNotContain(requests); - verify(connection).removeParticipant(player2); - verify(connection).hasPlayer1(); - assertThat(verify(connection).hasPlayer2()).isFalse(); - } -} diff --git a/Server/src/test/java/uulm/teamname/marvelous/server/lobby/pipelining/FilterEndRoundRequestSegmentTest.java b/Server/src/test/java/uulm/teamname/marvelous/server/lobby/pipelining/FilterEndRoundRequestSegmentTest.java deleted file mode 100644 index 20972e8..0000000 --- a/Server/src/test/java/uulm/teamname/marvelous/server/lobby/pipelining/FilterEndRoundRequestSegmentTest.java +++ /dev/null @@ -1,76 +0,0 @@ -package uulm.teamname.marvelous.server.lobby.pipelining; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import uulm.teamname.marvelous.gamelibrary.entities.EntityID; -import uulm.teamname.marvelous.gamelibrary.entities.EntityType; -import uulm.teamname.marvelous.gamelibrary.messages.ParticipantType; -import uulm.teamname.marvelous.gamelibrary.requests.Request; -import uulm.teamname.marvelous.gamelibrary.requests.RequestBuilder; -import uulm.teamname.marvelous.gamelibrary.requests.RequestType; -import uulm.teamname.marvelous.server.lobbymanager.Participant; -import uulm.teamname.marvelous.server.netconnector.Client; - -import java.util.ArrayList; -import java.util.concurrent.atomic.AtomicBoolean; - -import static org.mockito.Mockito.*; -import static org.assertj.core.api.Assertions.*; - -class FilterEndRoundRequestSegmentTest { - - FilterEndRoundRequestSegment segment; - - EntityID activeCharacter; - - Participant activeParticipant, inactiveParticipant; - - Request[] requests; - - @BeforeEach - void beforeEach() { - this.segment = new FilterEndRoundRequestSegment(this::getActiveCharacter); - - this.activeCharacter = new EntityID(EntityType.P1, 2); - this.activeParticipant = new Participant(mock(Client.class), null, ParticipantType.PlayerOne, false); - this.inactiveParticipant = new Participant(mock(Client.class), null, ParticipantType.PlayerTwo, false); - - requests = new Request[] { - new RequestBuilder(RequestType.EndRoundRequest).buildGameRequest() - }; - } - - private EntityID getActiveCharacter() { - return activeCharacter; - } - - @Test - @DisplayName("Request from active participant doesn't get filtered") - void packetFromActiveParticipantTest() { - var packet = new Packet(requests, activeParticipant); - var atomicBoolean = new AtomicBoolean(false); - - var processedPacket = (Packet) packet.clone(); - segment.processRequests(processedPacket, new ArrayList<>(), atomicBoolean); - - assertThat(processedPacket).isEqualTo(packet); - assertThat(atomicBoolean.get()).isFalse(); - } - - @Test - @DisplayName("Request from non-active participant gets flagged as an error") - void packetFromNonActiveParticipantTest() { - var packet = new Packet(requests, inactiveParticipant); - var atomicBoolean = new AtomicBoolean(false); - - var processedPacket = (Packet) packet.clone(); - segment.processRequests(processedPacket, new ArrayList<>(), atomicBoolean); - - // assertThat(processedPacket).isEqualTo(packet); is not necessary as there's no actual filtering going on - assertThat(atomicBoolean.get()).isTrue(); - } - - - -} diff --git a/Server/src/test/java/uulm/teamname/marvelous/server/lobby/pipelining/GameLogicSegmentTest.java b/Server/src/test/java/uulm/teamname/marvelous/server/lobby/pipelining/GameLogicSegmentTest.java deleted file mode 100644 index 0ca8e1b..0000000 --- a/Server/src/test/java/uulm/teamname/marvelous/server/lobby/pipelining/GameLogicSegmentTest.java +++ /dev/null @@ -1,46 +0,0 @@ -package uulm.teamname.marvelous.server.lobby.pipelining; - -import org.junit.jupiter.api.Test; -import uulm.teamname.marvelous.gamelibrary.events.Event; -import uulm.teamname.marvelous.gamelibrary.events.EventBuilder; -import uulm.teamname.marvelous.gamelibrary.requests.RequestBuilder; -import uulm.teamname.marvelous.gamelibrary.events.EventType; -import uulm.teamname.marvelous.gamelibrary.gamelogic.GameInstance; -import uulm.teamname.marvelous.gamelibrary.requests.Request; -import uulm.teamname.marvelous.gamelibrary.requests.RequestType; - -import java.util.List; -import java.util.ArrayList; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; - -import static org.mockito.Mockito.*; -import static org.assertj.core.api.Assertions.*; - -class GameLogicSegmentTest { - - @Test - void processRequests() { - var game = mock(GameInstance.class); - var request = new RequestBuilder(RequestType.DisconnectRequest).buildGameRequest(); - var event = new EventBuilder(EventType.DisconnectEvent).buildGameEvent(); - when(game.checkRequestsAndApply(any(ArrayList.class))).thenReturn(Optional.of(List.of(event))); - var abort = new AtomicBoolean(false); - - var segment = new GameLogicSegment(game); - - // note that DisconnectRequests are actually never passed to the GameLogic, ever. - var packet = new Packet( - new Request[] {request}, - null); - - var carrier = new ArrayList(1); - - segment.processRequests(packet, carrier, abort); - - assertThat(packet).isEmpty(); - assertThat(carrier).contains(event); - verify(game).checkRequestsAndApply(any(Packet.class)); - assertThat(abort.get()).isFalse(); - } -} diff --git a/Server/src/test/java/uulm/teamname/marvelous/server/lobby/pipelining/PacketTest.java b/Server/src/test/java/uulm/teamname/marvelous/server/lobby/pipelining/PacketTest.java deleted file mode 100644 index ace112f..0000000 --- a/Server/src/test/java/uulm/teamname/marvelous/server/lobby/pipelining/PacketTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package uulm.teamname.marvelous.server.lobby.pipelining; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import uulm.teamname.marvelous.gamelibrary.messages.ParticipantType; -import uulm.teamname.marvelous.gamelibrary.requests.Request; -import uulm.teamname.marvelous.gamelibrary.requests.RequestBuilder; -import uulm.teamname.marvelous.gamelibrary.requests.RequestType; -import uulm.teamname.marvelous.server.lobbymanager.Participant; -import uulm.teamname.marvelous.server.netconnector.Client; - -import static org.junit.jupiter.api.Assertions.*; -import static org.assertj.core.api.Assertions.*; - -class PacketTest { - Packet packet; - - @BeforeEach - void beforeEach(){ - var requests = new Request[] { - new RequestBuilder(RequestType.Req).buildGameRequest(), - new RequestBuilder(RequestType.MoveRequest).buildGameRequest() - }; - packet = new Packet(requests, null); - } - - @Test - void containsRequestTest(){ - assertThat(packet.containsRequestOfType(RequestType.Req)).isTrue(); - assertThat(packet.containsRequestOfType(RequestType.DisconnectRequest)).isFalse(); - } - - @Test - void removeRequestsOfTypesTest(){ - packet.removeRequestsOfTypes(RequestType.Req); - assertThat(packet).containsOnly(new RequestBuilder(RequestType.MoveRequest).buildGameRequest()); - } - - @Test - void removeRequestsNotOfTypesTest(){ - packet.removeRequestsNotOfTypes(RequestType.Req); - assertThat(packet).containsOnly(new RequestBuilder(RequestType.Req).buildGameRequest()); - } - - @Test - void getOriginTest(){ - var requests = new Request[] { - new RequestBuilder(RequestType.Req).buildGameRequest(), - new RequestBuilder(RequestType.MoveRequest).buildGameRequest() - }; - var participant = new Participant(new Client(null), "SomeLobby", ParticipantType.PlayerOne, false); - packet = new Packet(requests, participant); - - assertThat(packet.getOrigin()).isEqualTo(participant); - } - -} diff --git a/Server/src/test/java/uulm/teamname/marvelous/server/lobby/pipelining/PauseSegmentTest.java b/Server/src/test/java/uulm/teamname/marvelous/server/lobby/pipelining/PauseSegmentTest.java deleted file mode 100644 index 534b4a7..0000000 --- a/Server/src/test/java/uulm/teamname/marvelous/server/lobby/pipelining/PauseSegmentTest.java +++ /dev/null @@ -1,106 +0,0 @@ -package uulm.teamname.marvelous.server.lobby.pipelining; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import uulm.teamname.marvelous.gamelibrary.messages.ParticipantType; -import uulm.teamname.marvelous.gamelibrary.requests.Request; -import uulm.teamname.marvelous.gamelibrary.requests.RequestBuilder; -import uulm.teamname.marvelous.gamelibrary.requests.RequestType; -import uulm.teamname.marvelous.server.lobbymanager.LobbyConnection; -import uulm.teamname.marvelous.server.lobbymanager.Participant; - -import java.util.ArrayList; -import java.util.concurrent.atomic.AtomicBoolean; - -import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.mock; - -class PauseSegmentTest { - - PauseSegment pauseSegment; - - @BeforeEach - void setUp() { - pauseSegment = new PauseSegment(); - } - - @Test - void pauseGame() { - assertThat(pauseSegment.isPaused()).isFalse(); - pauseSegment.pauseGame(); - assertThat(pauseSegment.isPaused()).isTrue(); - pauseSegment.pauseGame(); - assertThat(pauseSegment.isPaused()).isTrue(); - } - - @Test - void pauseEnd() { - assertThat(pauseSegment.isPaused()).isFalse(); - pauseSegment.pauseEnd(); - assertThat(pauseSegment.isPaused()).isFalse(); - pauseSegment.pauseGame(); - assertThat(pauseSegment.isPaused()).isTrue(); - pauseSegment.pauseEnd(); - assertThat(pauseSegment.isPaused()).isFalse(); - } - - @Test - void doNotProcessEventsIfStopped() { - var requests = new Request[]{ - new RequestBuilder(RequestType.PauseStartRequest).buildGameRequest(), - new RequestBuilder(RequestType.Req).buildGameRequest(), - new RequestBuilder(RequestType.DisconnectRequest).buildGameRequest(), - new RequestBuilder(RequestType.EndRoundRequest).buildGameRequest(), - new RequestBuilder(RequestType.MoveRequest).buildGameRequest() - }; - var participant = mock(Participant.class); - var packet = new Packet(requests, participant); - AtomicBoolean abort = new AtomicBoolean(false); - pauseSegment.processRequests(packet, new ArrayList<>(), abort); - assertThat(packet).containsOnly(new RequestBuilder(RequestType.Req).buildGameRequest(), new RequestBuilder(RequestType.DisconnectRequest).buildGameRequest()); - } - - @Test - void doProcessEventsIfNotStopped(){ - var requests = new Request[]{ - new RequestBuilder(RequestType.PauseStopRequest).buildGameRequest(), - new RequestBuilder(RequestType.EndRoundRequest).buildGameRequest(), - new RequestBuilder(RequestType.MoveRequest).buildGameRequest() - }; - var participant = mock(Participant.class); - var packet = new Packet(requests, participant); - AtomicBoolean abort = new AtomicBoolean(false); - pauseSegment.pauseGame(); - pauseSegment.processRequests(packet, new ArrayList<>(), abort); - assertThat(packet).containsOnly( - new RequestBuilder(RequestType.EndRoundRequest).buildGameRequest(), - new RequestBuilder(RequestType.MoveRequest).buildGameRequest() - ); - } - - @Test - void pauseRequestWhilePaused(){ - var requests = new Request[]{ - new RequestBuilder(RequestType.PauseStartRequest).buildGameRequest(), - }; - var participant = mock(Participant.class); - var packet = new Packet(requests, participant); - AtomicBoolean abort = new AtomicBoolean(false); - pauseSegment.pauseGame(); - pauseSegment.processRequests(packet, new ArrayList<>(), abort); - assertThat(abort.get()).isTrue(); - } - - @Test - void unpauseRequestWhenNotPaused(){ - var requests = new Request[]{ - new RequestBuilder(RequestType.PauseStopRequest).buildGameRequest(), - }; - var participant = mock(Participant.class); - var packet = new Packet(requests, participant); - AtomicBoolean abort = new AtomicBoolean(false); - pauseSegment.pauseEnd(); - pauseSegment.processRequests(packet, new ArrayList<>(), abort); - assertThat(abort.get()).isTrue(); - } -} \ No newline at end of file diff --git a/Server/src/test/java/uulm/teamname/marvelous/server/lobby/pipelining/PipelineTest.java b/Server/src/test/java/uulm/teamname/marvelous/server/lobby/pipelining/PipelineTest.java deleted file mode 100644 index c35ccda..0000000 --- a/Server/src/test/java/uulm/teamname/marvelous/server/lobby/pipelining/PipelineTest.java +++ /dev/null @@ -1,66 +0,0 @@ -package uulm.teamname.marvelous.server.lobby.pipelining; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import uulm.teamname.marvelous.gamelibrary.requests.Request; -import uulm.teamname.marvelous.gamelibrary.requests.RequestBuilder; -import uulm.teamname.marvelous.gamelibrary.requests.RequestType; -import uulm.teamname.marvelous.server.lobby.Lobby; -import uulm.teamname.marvelous.server.lobbymanager.Participant; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; - -import static org.junit.jupiter.api.Assertions.*; -import static org.assertj.core.api.Assertions.*; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.*; - -class PipelineTest { - Pipeline pipeline; - @BeforeEach - void beforeEach(){ - pipeline = new Pipeline(); - } - - @Test - void addSegmentsTest(){ - Lobby lobby = mock(Lobby.class); - PauseSegment pauseSegment = new PauseSegment(); - DisconnectSegment disconnectSegment = new DisconnectSegment(lobby); - pipeline.addSegment(pauseSegment); - assertThat(pipeline.contains(pauseSegment)).isTrue(); - assertThat(pipeline.contains(disconnectSegment)).isFalse(); - pipeline.addSegment(disconnectSegment); - assertThat(pipeline.contains(pauseSegment)).isTrue(); - assertThat(pipeline.contains(disconnectSegment)).isTrue(); - } - - @Test - void processRequestTest(){ - var segment = mock(Segment.class); - var segment2 = mock(Segment.class); - var segment3 = mock(Segment.class); - - var requests = new Request[]{ - new RequestBuilder(RequestType.PauseStartRequest).buildGameRequest(), - new RequestBuilder(RequestType.Req).buildGameRequest() - }; - - Participant participant = mock(Participant.class); - - var abort = new AtomicBoolean(false); - - Packet packet = new Packet(requests, participant); - - pipeline.addSegment(segment) - .addSegment(segment2) - .addSegment(segment3); - - pipeline.processRequests(requests, participant); - verify(segment).processRequests(eq(packet), eq(new ArrayList<>()), any(AtomicBoolean.class)); - verify(segment2).processRequests(eq(packet), eq(new ArrayList<>()), any(AtomicBoolean.class)); - verify(segment3).processRequests(eq(packet), eq(new ArrayList<>()), any(AtomicBoolean.class)); - } -} \ No newline at end of file diff --git a/Server/src/test/java/uulm/teamname/marvelous/server/lobby/pipelining/PlayerFilterSegmentTest.java b/Server/src/test/java/uulm/teamname/marvelous/server/lobby/pipelining/PlayerFilterSegmentTest.java deleted file mode 100644 index cdde662..0000000 --- a/Server/src/test/java/uulm/teamname/marvelous/server/lobby/pipelining/PlayerFilterSegmentTest.java +++ /dev/null @@ -1,118 +0,0 @@ -package uulm.teamname.marvelous.server.lobby.pipelining; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import uulm.teamname.marvelous.gamelibrary.IntVector2; -import uulm.teamname.marvelous.gamelibrary.entities.EntityID; -import uulm.teamname.marvelous.gamelibrary.entities.EntityType; -import uulm.teamname.marvelous.gamelibrary.events.Event; -import uulm.teamname.marvelous.gamelibrary.messages.ParticipantType; -import uulm.teamname.marvelous.gamelibrary.requests.Request; -import uulm.teamname.marvelous.gamelibrary.requests.RequestBuilder; -import uulm.teamname.marvelous.gamelibrary.requests.RequestType; -import uulm.teamname.marvelous.server.lobbymanager.Participant; -import uulm.teamname.marvelous.server.netconnector.Client; - -import java.util.ArrayList; -import java.util.concurrent.atomic.AtomicBoolean; - -import static org.mockito.Mockito.*; -import static org.junit.jupiter.api.Assertions.*; -import static org.assertj.core.api.Assertions.*; - -class PlayerFilterSegmentTest { - - PlayerFilterSegment segment; - AtomicBoolean abort; - - @BeforeEach - void beforeEach() { - segment = new PlayerFilterSegment(); - abort = new AtomicBoolean(false); - } - - @Test - void validRequestsRemainUntouched() { - var participant = new Participant(mock(Client.class), null, ParticipantType.PlayerOne, false); - - var requests = new Request[] { - new RequestBuilder(RequestType.MeleeAttackRequest) - .withOriginField(new IntVector2(1, 4)) - .withTargetField(new IntVector2(2, 4)) - .withOriginEntity(new EntityID(EntityType.P1, 3)) - .buildCharacterRequest(), - new RequestBuilder(RequestType.MeleeAttackRequest) - .withOriginField(new IntVector2(2, 4)) - .withTargetField(new IntVector2(3, 5)) - .withOriginEntity(new EntityID(EntityType.P1, 3)) - .withTargetEntity(new EntityID(EntityType.P2, 3)) - .withValue(14) - .buildCharacterRequest() - }; - - var packet = new Packet(requests, participant); - var carrier = new ArrayList(); - - assertThatNoException().isThrownBy(() -> segment.processRequests(packet, carrier, abort)); - - assertThat(packet.toArray(new Request[0])) - .isEqualTo(requests); - assertThat(carrier).isEmpty(); - assertThat(abort).isFalse(); - - } - - @Test - void invalidRequestsTriggerAbort() { - var participant = new Participant(mock(Client.class), null, ParticipantType.PlayerOne, false); - - var requests = new Request[] { - new RequestBuilder(RequestType.MeleeAttackRequest) - .withOriginField(new IntVector2(1, 4)) - .withTargetField(new IntVector2(2, 4)) - .withOriginEntity(new EntityID(EntityType.P2, 3)) - .buildCharacterRequest(), - new RequestBuilder(RequestType.MeleeAttackRequest) - .withOriginField(new IntVector2(2, 4)) - .withTargetField(new IntVector2(3, 5)) - .withOriginEntity(new EntityID(EntityType.P2, 3)) - .withTargetEntity(new EntityID(EntityType.P1, 3)) - .withValue(14) - .buildCharacterRequest() - }; - - var packet = new Packet(requests, participant); - var carrier = new ArrayList(); - - assertThatNoException().isThrownBy(() -> segment.processRequests(packet, carrier, abort)); - - assertThat(packet.toArray(new Request[0])) - .isEqualTo(requests); - assertThat(carrier).isEmpty(); - assertThat(abort).isTrue(); - } - - - @Test - void gameRequestsRemainUntouched() { - var participant = new Participant(mock(Client.class), null, ParticipantType.PlayerOne, false); - - var requests = new Request[] { - new RequestBuilder(RequestType.Req).buildGameRequest(), - new RequestBuilder(RequestType.EndRoundRequest).buildGameRequest(), - new RequestBuilder(RequestType.DisconnectRequest).buildGameRequest() - - }; - - var packet = new Packet(requests, participant); - var carrier = new ArrayList(); - - assertThatNoException().isThrownBy(() -> segment.processRequests(packet, carrier, abort)); - - assertThat(packet.toArray(new Request[0])) - .isEqualTo(requests); - assertThat(carrier).isEmpty(); - assertThat(abort).isFalse(); - } - -} diff --git a/Server/src/test/java/uulm/teamname/marvelous/server/lobby/pipelining/RequestGameLogicSegmentTest.java b/Server/src/test/java/uulm/teamname/marvelous/server/lobby/pipelining/RequestGameLogicSegmentTest.java deleted file mode 100644 index 01f3585..0000000 --- a/Server/src/test/java/uulm/teamname/marvelous/server/lobby/pipelining/RequestGameLogicSegmentTest.java +++ /dev/null @@ -1,51 +0,0 @@ -package uulm.teamname.marvelous.server.lobby.pipelining; - -import org.junit.jupiter.api.Test; -import uulm.teamname.marvelous.gamelibrary.events.Event; -import uulm.teamname.marvelous.gamelibrary.events.GamestateEvent; -import uulm.teamname.marvelous.gamelibrary.gamelogic.GameInstance; -import uulm.teamname.marvelous.gamelibrary.messages.client.RequestMessage; -import uulm.teamname.marvelous.gamelibrary.messages.server.EventMessage; -import uulm.teamname.marvelous.gamelibrary.requests.Request; -import uulm.teamname.marvelous.gamelibrary.requests.RequestBuilder; -import uulm.teamname.marvelous.gamelibrary.requests.RequestType; -import uulm.teamname.marvelous.server.lobbymanager.Participant; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -class RequestGameLogicSegmentTest { - - @Test - void requestGamestateTest(){ - var game = mock(GameInstance.class); - var gamestateEvent = mock(GamestateEvent.class); - var segment = new RequestGameStateSegment(game); - var requests = new Request[]{ - new RequestBuilder(RequestType.Req).buildGameRequest(), - new RequestBuilder(RequestType.DisconnectRequest).buildGameRequest() - }; - var participant = mock(Participant.class); - var packet = new Packet(requests, participant); - - var message = new EventMessage(); - message.messages = new Event[] {gamestateEvent}; - - when(game.getGameStateEvent()).thenReturn(gamestateEvent); - - AtomicBoolean abort = new AtomicBoolean(false); - List carrier = new ArrayList<>(); - - segment.processRequests(packet, carrier, abort); - assertThat(packet).isEmpty(); - assertThat(carrier).isEmpty(); - verify(game).getGameStateEvent(); - verify(participant).sendMessage(message); - } - -} diff --git a/Server/src/test/java/uulm/teamname/marvelous/server/lobbymanager/RandomWordGeneratorTest.java b/Server/src/test/java/uulm/teamname/marvelous/server/lobbymanager/RandomWordGeneratorTest.java deleted file mode 100644 index 05893b4..0000000 --- a/Server/src/test/java/uulm/teamname/marvelous/server/lobbymanager/RandomWordGeneratorTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package uulm.teamname.marvelous.server.lobbymanager; - -import org.junit.jupiter.api.Test; - -import static org.mockito.Mockito.*; -import static org.junit.jupiter.api.Assertions.*; -import static org.assertj.core.api.Assertions.*; - -class RandomWordGeneratorTest { - - @Test - void generatesAString() { - // System.out.println(RandomWordGenerator.generateTwoWords()); - assertThat(RandomWordGenerator.generateTwoWords()).isInstanceOf(String.class); - } - -} diff --git a/Server/src/test/java/uulm/teamname/marvelous/server/netconnector/ClientTest.java b/Server/src/test/java/uulm/teamname/marvelous/server/netconnector/ClientTest.java deleted file mode 100644 index ec556a7..0000000 --- a/Server/src/test/java/uulm/teamname/marvelous/server/netconnector/ClientTest.java +++ /dev/null @@ -1,53 +0,0 @@ -package uulm.teamname.marvelous.server.netconnector; - -import org.java_websocket.WebSocket; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import uulm.teamname.marvelous.gamelibrary.json.JSON; -import uulm.teamname.marvelous.gamelibrary.messages.ErrorMessage; - -import static org.mockito.Mockito.*; -import static org.junit.jupiter.api.Assertions.*; -import static org.assertj.core.api.Assertions.*; - -class ClientTest { - - Client client; - WebSocket socket; - SUID suid; - - @BeforeEach - void setUp() { - socket = mock(WebSocket.class); - suid = new SUID("ClientName", "DeviceID"); - - client = new Client(socket); - } - - @Test - void clientGetsCreatedEmpty() { - assertThat(client.getState()).isEqualTo(ClientState.Blank); - assertThat(client.getId()).isNull(); - assertThat(client.getSocket()).isEqualTo(socket); - } - - @Test - void sendError() { - client.sendError("SomeMessage"); - - verify(socket).send("{\"messageType\":\"ERROR\",\"message\":\"SomeMessage\",\"type\":0}"); - } - - @Test - void sendMessage() { - var stringRepresentingErrorMessage = "{\"messageType\":\"ERROR\",\"message\":\"SomeMessage\",\"type\":0}"; - - var errorMessage = new ErrorMessage(); - errorMessage.message = "SomeMessage"; - errorMessage.type = 0; - - client.sendMessage(errorMessage); - - verify(socket).send(stringRepresentingErrorMessage); - } -} diff --git a/Server/src/test/java/uulm/teamname/marvelous/server/netconnector/UserManagerTest.java b/Server/src/test/java/uulm/teamname/marvelous/server/netconnector/UserManagerTest.java deleted file mode 100644 index 98fd48a..0000000 --- a/Server/src/test/java/uulm/teamname/marvelous/server/netconnector/UserManagerTest.java +++ /dev/null @@ -1,63 +0,0 @@ -package uulm.teamname.marvelous.server.netconnector; - -import org.java_websocket.WebSocket; -import org.java_websocket.handshake.ClientHandshake; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import uulm.teamname.marvelous.gamelibrary.messages.ParticipantType; -import uulm.teamname.marvelous.gamelibrary.messages.client.*; -import uulm.teamname.marvelous.server.lobbymanager.Participant; - -import java.lang.reflect.InvocationTargetException; - -import static org.mockito.Mockito.*; -import static org.assertj.core.api.Assertions.*; - -class UserManagerTest { - - UserManager manager; - - Client client1, client2; - WebSocket socket1, socket2; - SUID suid1, suid2; - - @BeforeEach - void beforeEach() - throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { - socket1 = mock(WebSocket.class); - client1 = spy(new Client(socket1)); - suid1 = new SUID("name1", "devID1"); - when(client1.getId()).thenReturn(suid1); - when(client1.getSocket()).thenReturn(socket1); - - socket2 = mock(WebSocket.class); - client2 = spy(new Client(socket2)); - suid2 = new SUID("name2", "devID2"); - when(client2.getId()).thenReturn(suid2); - when(client2.getSocket()).thenReturn(socket2); - - var c = UserManager.class.getDeclaredConstructor(); - c.setAccessible(true); - manager = spy(c.newInstance()); - } - - @Test - void userIsConnectedTest() { - assertThat(manager.getUserCount()).isZero(); - assertThat(manager.containsConnection(socket1)).isFalse(); - manager.connectUser(socket1); - assertThat(manager.getUserCount()).isEqualTo(1); - assertThat(manager.containsConnection(socket1)).isTrue(); - - verify(socket1, never()).send(any(String.class)); - } - - @Test - void helloServerMessagesGetAssignedProperly() { - manager.messageReceived( - socket1, - "{\"messageType\":\"HELLO_SERVER\",\"name\":\"SomeAwesomeName\",\"deviceID\":\"YAY\"}"); - - // TODO: test this - } -}