Compare commits
	
		
			9 Commits
		
	
	
		
			server
			...
			ac65dd2f29
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| ac65dd2f29 | |||
| fc2bf12dfa | |||
| 382b6a859d | |||
| cefee10880 | |||
| d6a0a5f732 | |||
| 38da3329d7 | |||
| 3abacf0f8b | |||
| 02a2805a8a | |||
| 582bd83a2d | 
							
								
								
									
										10
									
								
								.idea/runConfigurations.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								.idea/runConfigurations.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="RunConfigurationProducerService"> | ||||
|     <option name="ignoredProducers"> | ||||
|       <set> | ||||
|         <option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" /> | ||||
|       </set> | ||||
|     </option> | ||||
|   </component> | ||||
| </project> | ||||
							
								
								
									
										12
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								Dockerfile
									
									
									
									
									
								
							| @ -3,15 +3,15 @@ RUN mkdir logs | ||||
|  | ||||
| COPY Server/build/libs/Server.jar Server.jar | ||||
|  | ||||
| COPY configs/asgard.scenario.json default/asgard.scenario.json | ||||
| COPY configs/marvelheros.character.json default/marvelheroes.character.json | ||||
| COPY configs/matchconfig_1.game.json default/matchconfig.game.json | ||||
| COPY configs/testmapwithportals.scenario.json default/testmapwithportals.scenario.json | ||||
| COPY configs/characterConfig.character.json default/characterConfig.character.json | ||||
| COPY configs/gameConfig.game.json default/gameConfig.game.json | ||||
|  | ||||
| ARG MMU_LOG_LEVEL=3 | ||||
|  | ||||
| ARG MMU_CONF_MATCH=default/matchconfig.game.json | ||||
| ARG MMU_CONF_CHARS=default/marvelheroes.character.json | ||||
| ARG MMU_CONF_SCENARIO=default/asgard.scenario.json | ||||
| ARG MMU_CONF_MATCH=default/gameConfig.game.json | ||||
| ARG MMU_CONF_CHARS=default/characterConfig.character.json | ||||
| ARG MMU_CONF_SCENARIO=default/testmapwithportals.scenario.json | ||||
|  | ||||
| ARG MMU_REPLAY_DIR=/replays | ||||
|  | ||||
|  | ||||
| @ -80,7 +80,7 @@ sonarqube { | ||||
|     } | ||||
| } | ||||
|  | ||||
| var mainClassName = "uulm.teamname.marvelous.server.Server" | ||||
| var mainClassName = "uulm.teamname.marvelous.server.ServerApplication" | ||||
|  | ||||
| jar { | ||||
|     manifest { | ||||
|  | ||||
| @ -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 <b>BEFORE ANY LOGGING OPERATIONS</b> . | ||||
|      */ | ||||
|     @SuppressWarnings("DuplicateBranchesInSwitch") | ||||
|     private static void setLogLevel(int logLevel) { | ||||
|         Map<String, String> 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; | ||||
|     } | ||||
| } | ||||
| @ -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() { | ||||
|  | ||||
| @ -0,0 +1,275 @@ | ||||
| package uulm.teamname.marvelous.server.game; | ||||
|  | ||||
| import uulm.teamname.marvelous.gamelibrary.ArrayTools; | ||||
| import uulm.teamname.marvelous.gamelibrary.config.CharacterProperties; | ||||
| 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.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.*; | ||||
| import java.util.concurrent.ScheduledExecutorService; | ||||
| import java.util.concurrent.ScheduledFuture; | ||||
| import java.util.concurrent.ScheduledThreadPoolExecutor; | ||||
| import java.util.concurrent.TimeUnit; | ||||
|  | ||||
| public class GameSession { | ||||
|     public final String id = UUID.randomUUID().toString(); | ||||
|  | ||||
|     private final ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(3); | ||||
|  | ||||
|     public final HashMap<ParticipantType, CharacterProperties[]> characterChoices = new HashMap<>(); | ||||
|     private final HashMap<ParticipantType, Integer[]> characterIndices = new HashMap<>(); | ||||
|     private final HashMap<ParticipantType, List<Integer>> characterSelection = new HashMap<>(); | ||||
|  | ||||
|     private final HashMap<ParticipantType, Integer> badRequests = new HashMap<>(); | ||||
|  | ||||
|     private ParticipantType turnWaiting; | ||||
|     private ScheduledFuture<?> turnTimer; | ||||
|  | ||||
|     private boolean started = false; | ||||
|  | ||||
|     private GameInstance instance; | ||||
|     private boolean paused = false; | ||||
|  | ||||
|     private Pipeline pipeline; | ||||
|  | ||||
|     public GameSession() { | ||||
|         List<CharacterProperties> 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<Integer> 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<List<Event>> 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<Event> 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; | ||||
|  | ||||
|         turnWaiting = instance.state.getActiveCharacter().type == EntityType.P1 ? ParticipantType.PlayerOne : ParticipantType.PlayerTwo; | ||||
|         turnTimer = scheduler.schedule(this::timeout, ServerApplication.getPartyConfig().maxRoundTime, TimeUnit.SECONDS); | ||||
|     } | ||||
|  | ||||
|     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<Event> 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.put(source.getType(), 0); | ||||
|  | ||||
|         if(turnTimer != null) { | ||||
|             turnTimer.cancel(true); | ||||
|         } | ||||
|  | ||||
|         turnWaiting = source.getType(); | ||||
|         turnTimer = scheduler.schedule(this::timeout, ServerApplication.getPartyConfig().maxRoundTime, TimeUnit.SECONDS); | ||||
|     } | ||||
|  | ||||
|     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.put(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 void timeout() { | ||||
|         for(Client player: ServerApplication.getSession().getPlayers()) { | ||||
|             if(player != null && player.getType() != turnWaiting) { | ||||
|                 EventMessage notification = new EventMessage(); | ||||
|                 notification.messages = new Event[] { new EventBuilder(EventType.TurnTimeoutEvent).buildGameEvent() }; | ||||
|                 player.sendMessage(notification); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         EventMessage message = new EventMessage(); | ||||
|         message.messages = instance.endTurn().toArray(new Event[0]); | ||||
|         ServerApplication.getSession().broadcast(message); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     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; | ||||
|     } | ||||
| } | ||||
| @ -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<Event> carrier, AtomicBoolean abort) { | ||||
|         Logger.trace("DisconnectSegment received {} requests.", packet.size()); | ||||
|         if(packet.containsRequestOfType(RequestType.DisconnectRequest)) { | ||||
|             packet.getOrigin().disconnect(); | ||||
|             packet.clear(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -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<Event> 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); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -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<Event> 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); | ||||
|         } | ||||
| @ -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<Request> { | ||||
| 
 | ||||
|     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<Request> { | ||||
|         this.removeIf(request -> !listOfTypes.contains(request.type)); | ||||
|     } | ||||
| 
 | ||||
|     public Participant getOrigin() { | ||||
|     public Client getOrigin() { | ||||
|         return origin; | ||||
|     } | ||||
| 
 | ||||
| @ -69,6 +69,7 @@ public class Packet extends ArrayList<Request> { | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @SuppressWarnings("MethodDoesntCallSuperMethod") | ||||
|     public Object clone() { | ||||
|         return new Packet(this.toArray(new Request[0]), this.origin); | ||||
|     } | ||||
| @ -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<Event> 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, | ||||
| @ -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<List<Event>> processRequests(Request[] requests, Participant origin) { | ||||
|     public Optional<List<Event>> 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); | ||||
| @ -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<Event> 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); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
| } | ||||
| @ -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<Event> 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(); | ||||
|         } | ||||
| @ -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<Event> carrier, AtomicBoolean abort); | ||||
|     void processRequests(Packet packet, List<Event> carrier, AtomicBoolean abort); | ||||
| } | ||||
| @ -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); | ||||
|     } | ||||
| } | ||||
| @ -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<Integer> player1Characters, | ||||
|             List<Integer> 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<List<Event>> 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<Participant> 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<Participant> 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<Event> 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 + | ||||
|                 '}'; | ||||
|     } | ||||
| } | ||||
| @ -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<Participant> player1AlmostTimeout; | ||||
|     private ScheduledFuture<Participant> player1Timeout; | ||||
|     private ScheduledFuture<Participant> player2AlmostTimeout; | ||||
|     private ScheduledFuture<Participant> player2Timeout; | ||||
|  | ||||
|     private final BiConsumer<Participant, Integer> almostTimeoutCallback; | ||||
|     private final Consumer<Participant> 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<Participant, Integer> almostTimeoutCallback, | ||||
|                         Consumer<Participant> 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()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -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<Participant> callback; | ||||
|     private final int maxRoundTime; | ||||
|  | ||||
|     private ScheduledFuture<Participant> current; | ||||
|  | ||||
|     public TurnTimeoutTimer(int maxRoundTime, Consumer<Participant> 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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -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<Event> 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(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -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<EntityID> 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<EntityID> getActiveCharacterCallback) { | ||||
|         this.getActiveCharacterCallback = getActiveCharacterCallback; | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void processRequests(Packet packet, List<Event> 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); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -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<Participant> spectators = new HashSet<>(10); | ||||
|     private final Map<SUID, List<Integer>> selection = new HashMap<>(2); | ||||
|     public final Map<ParticipantType, CharacterProperties[]> options = new HashMap<>(2); | ||||
|  | ||||
|     private final BlockingQueue<Tuple<Participant, Request[]>> requestQueue = new LinkedBlockingQueue<>(); | ||||
|  | ||||
|     private Lobby lobby; | ||||
|  | ||||
|     /** Creates a new LobbyConnection */ | ||||
|     public LobbyConnection(String gameID) { | ||||
|         this.gameID = gameID; | ||||
|  | ||||
|         Tuple<CharacterProperties[], CharacterProperties[]> 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<Participant> 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<Participant, Request[]> 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<Participant, Request[]> pollQueueAsync() { | ||||
|         Tuple<Participant, Request[]> 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<Event> 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); | ||||
|     } | ||||
| } | ||||
| @ -1,7 +0,0 @@ | ||||
| package uulm.teamname.marvelous.server.lobbymanager; | ||||
|  | ||||
| public enum LobbyConnectionState { | ||||
|     Waiting, | ||||
|     Started, | ||||
|     Aborted | ||||
| } | ||||
| @ -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<String, LobbyConnection> lobbies = new HashMap<>(); | ||||
|  | ||||
|     private final HashMap<SUID, Participant> 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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -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<LobbyConnection, Thread> 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() { | ||||
|  | ||||
|     } | ||||
| } | ||||
| @ -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; | ||||
|     } | ||||
| } | ||||
| @ -1,7 +0,0 @@ | ||||
| package uulm.teamname.marvelous.server.lobbymanager; | ||||
|  | ||||
| public enum ParticipantState { | ||||
|     Assigned, | ||||
|     Selected, | ||||
|     Playing | ||||
| } | ||||
| @ -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", | ||||
|     }; | ||||
| } | ||||
| @ -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<String> 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()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -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 | ||||
| } | ||||
| @ -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<WebSocket, Client> 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()); | ||||
|     } | ||||
| } | ||||
| @ -1,4 +1,4 @@ | ||||
| package uulm.teamname.marvelous.server.netconnector; | ||||
| package uulm.teamname.marvelous.server.net; | ||||
| 
 | ||||
| import java.util.Objects; | ||||
| 
 | ||||
| @ -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<SocketEvent> 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<SUID> 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<Client> spectators = new ArrayList<>(); | ||||
|  | ||||
|     public Client[] getPlayers() { | ||||
|         return players; | ||||
|     } | ||||
|     public ArrayList<Client> getSpectators() { | ||||
|         return spectators; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     private void onConnect(Client client) { | ||||
|         //do nothing ? | ||||
|     } | ||||
|  | ||||
|     private void onMessage(Client client, String message) { | ||||
|         Logger.trace("Parsing message..."); | ||||
|         Optional<BasicMessage> 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<String> 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); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,6 @@ | ||||
| package uulm.teamname.marvelous.server.net; | ||||
|  | ||||
| public enum SessionState { | ||||
|     Pending, | ||||
|     Running | ||||
| } | ||||
| @ -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; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,7 @@ | ||||
| package uulm.teamname.marvelous.server.net; | ||||
|  | ||||
| public enum SocketEventType { | ||||
|     Connect, | ||||
|     Message, | ||||
|     Disconnect | ||||
| } | ||||
| @ -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<String> 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; | ||||
|     } | ||||
| } | ||||
| @ -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); | ||||
|     } | ||||
| } | ||||
| @ -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<WebSocket, Client> 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<BasicMessage> 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<String> 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); | ||||
|     } | ||||
| } | ||||
| @ -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); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -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,72 @@ 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<Server> serverMock; | ||||
| @SuppressWarnings({"ResultOfMethodCallIgnored", "SameParameterValue", "unchecked"}) | ||||
| class MarvelousServerApplicationTest extends BaseGameLogicTest { | ||||
|     private static ServerSession session; | ||||
|     private static MockedStatic<ServerApplication> serverMock; | ||||
|     private static JSON json; | ||||
| 
 | ||||
|     @BeforeAll | ||||
|     static void start() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException { | ||||
|         Constructor<UserManager> um = UserManager.class.getDeclaredConstructor(); | ||||
|         um.setAccessible(true); | ||||
|         um.newInstance(); | ||||
|         Constructor<LobbyManager> 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}: <TEST> {message}"); | ||||
|         } | ||||
|    } | ||||
| 
 | ||||
| 
 | ||||
|     @Test | ||||
|     @Disabled | ||||
|     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 +103,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 +118,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 +126,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 +137,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 +148,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 +164,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<String> 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<String> 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 <T extends BasicMessage> void ensureReceived(WebSocket c, T response) { | ||||
|     private <T extends BasicMessage> void ensureReceived(Client c, T response) { | ||||
|         boolean found = false; | ||||
| 
 | ||||
|         for(Invocation i: mockingDetails(c).getInvocations()) { | ||||
|         for(Invocation i: mockingDetails(c.getSocket()).getInvocations()) { | ||||
|             Optional<BasicMessage> received = json.parse(i.getArgument(0, String.class)); | ||||
| 
 | ||||
|             if(received.isPresent()) { | ||||
| @ -227,7 +211,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 +249,7 @@ class MarvelousServerTest extends BaseGameLogicTest { | ||||
|         Optional<BasicMessage> 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 +274,7 @@ class MarvelousServerTest extends BaseGameLogicTest { | ||||
| 
 | ||||
|     @AfterAll | ||||
|     static void stop() { | ||||
|         session.stop(); | ||||
|         serverMock.close(); | ||||
|     } | ||||
| } | ||||
| @ -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)); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -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(); | ||||
|     } | ||||
| } | ||||
| @ -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(); | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
| } | ||||
| @ -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<Event>(1); | ||||
|  | ||||
|         segment.processRequests(packet, carrier, abort); | ||||
|  | ||||
|         assertThat(packet).isEmpty(); | ||||
|         assertThat(carrier).contains(event); | ||||
|         verify(game).checkRequestsAndApply(any(Packet.class)); | ||||
|         assertThat(abort.get()).isFalse(); | ||||
|     } | ||||
| } | ||||
| @ -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); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -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(); | ||||
|     } | ||||
| } | ||||
| @ -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)); | ||||
|     } | ||||
| } | ||||
| @ -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<Event>(); | ||||
|  | ||||
|         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<Event>(); | ||||
|  | ||||
|         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<Event>(); | ||||
|  | ||||
|         assertThatNoException().isThrownBy(() -> segment.processRequests(packet, carrier, abort)); | ||||
|  | ||||
|         assertThat(packet.toArray(new Request[0])) | ||||
|                 .isEqualTo(requests); | ||||
|         assertThat(carrier).isEmpty(); | ||||
|         assertThat(abort).isFalse(); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -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<Event> carrier = new ArrayList<>(); | ||||
|  | ||||
|         segment.processRequests(packet, carrier, abort); | ||||
|         assertThat(packet).isEmpty(); | ||||
|         assertThat(carrier).isEmpty(); | ||||
|         verify(game).getGameStateEvent(); | ||||
|         verify(participant).sendMessage(message); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -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); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -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); | ||||
|     } | ||||
| } | ||||
| @ -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 | ||||
|     } | ||||
| } | ||||
							
								
								
									
										244
									
								
								configs/characterConfig.character.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										244
									
								
								configs/characterConfig.character.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,244 @@ | ||||
| { | ||||
|   "characters": [ | ||||
|     { | ||||
|       "characterID": 1, | ||||
|       "name": "Rocket Raccoon", | ||||
|       "HP": 54, | ||||
|       "MP": 8, | ||||
|       "AP": 12, | ||||
|       "meleeDamage": 9, | ||||
|       "rangeCombatDamage": 3, | ||||
|       "rangeCombatReach": 4 | ||||
|     }, | ||||
|     { | ||||
|       "characterID": 2, | ||||
|       "name": "Quicksilver", | ||||
|       "HP": 138, | ||||
|       "MP": 12, | ||||
|       "AP": 5, | ||||
|       "meleeDamage": 6, | ||||
|       "rangeCombatDamage": 3, | ||||
|       "rangeCombatReach": 12 | ||||
|     }, | ||||
|     { | ||||
|       "characterID": 3, | ||||
|       "name": "Hulk", | ||||
|       "HP": 69, | ||||
|       "MP": 11, | ||||
|       "AP": 5, | ||||
|       "meleeDamage": 6, | ||||
|       "rangeCombatDamage": 5, | ||||
|       "rangeCombatReach": 1 | ||||
|     }, | ||||
|     { | ||||
|       "characterID": 4, | ||||
|       "name": "Black Widow", | ||||
|       "HP": 54, | ||||
|       "MP": 1, | ||||
|       "AP": 13, | ||||
|       "meleeDamage": 14, | ||||
|       "rangeCombatDamage": 5, | ||||
|       "rangeCombatReach": 5 | ||||
|     }, | ||||
|     { | ||||
|       "characterID": 5, | ||||
|       "name": "Hawkeye", | ||||
|       "HP": 98, | ||||
|       "MP": 16, | ||||
|       "AP": 5, | ||||
|       "meleeDamage": 9, | ||||
|       "rangeCombatDamage": 3, | ||||
|       "rangeCombatReach": 1 | ||||
|     }, | ||||
|     { | ||||
|       "characterID": 6, | ||||
|       "name": "Captain America", | ||||
|       "HP": 82, | ||||
|       "MP": 4, | ||||
|       "AP": 6, | ||||
|       "meleeDamage": 6, | ||||
|       "rangeCombatDamage": 6, | ||||
|       "rangeCombatReach": 1 | ||||
|     }, | ||||
|     { | ||||
|       "characterID": 7, | ||||
|       "name": "Spiderman", | ||||
|       "HP": 133, | ||||
|       "MP": 6, | ||||
|       "AP": 5, | ||||
|       "meleeDamage": 7, | ||||
|       "rangeCombatDamage": 3, | ||||
|       "rangeCombatReach": 2 | ||||
|     }, | ||||
|     { | ||||
|       "characterID": 8, | ||||
|       "name": "Dr. Strange", | ||||
|       "HP": 51, | ||||
|       "MP": 5, | ||||
|       "AP": 7, | ||||
|       "meleeDamage": 6, | ||||
|       "rangeCombatDamage": 2, | ||||
|       "rangeCombatReach": 5 | ||||
|     }, | ||||
|     { | ||||
|       "characterID": 9, | ||||
|       "name": "Iron Man", | ||||
|       "HP": 89, | ||||
|       "MP": 2, | ||||
|       "AP": 1, | ||||
|       "meleeDamage": 16, | ||||
|       "rangeCombatDamage": 1, | ||||
|       "rangeCombatReach": 18 | ||||
|     }, | ||||
|     { | ||||
|       "characterID": 10, | ||||
|       "name": "Black Panther", | ||||
|       "HP": 62, | ||||
|       "MP": 14, | ||||
|       "AP": 1, | ||||
|       "meleeDamage": 9, | ||||
|       "rangeCombatDamage": 6, | ||||
|       "rangeCombatReach": 9 | ||||
|     }, | ||||
|     { | ||||
|       "characterID": 11, | ||||
|       "name": "Thor", | ||||
|       "HP": 72, | ||||
|       "MP": 9, | ||||
|       "AP": 3, | ||||
|       "meleeDamage": 8, | ||||
|       "rangeCombatDamage": 1, | ||||
|       "rangeCombatReach": 1 | ||||
|     }, | ||||
|     { | ||||
|       "characterID": 12, | ||||
|       "name": "Captain Marvel", | ||||
|       "HP": 100, | ||||
|       "MP": 1, | ||||
|       "AP": 1, | ||||
|       "meleeDamage": 12, | ||||
|       "rangeCombatDamage": 11, | ||||
|       "rangeCombatReach": 6 | ||||
|     }, | ||||
|     { | ||||
|       "characterID": 13, | ||||
|       "name": "Groot", | ||||
|       "HP": 112, | ||||
|       "MP": 4, | ||||
|       "AP": 6, | ||||
|       "meleeDamage": 15, | ||||
|       "rangeCombatDamage": 9, | ||||
|       "rangeCombatReach": 6 | ||||
|     }, | ||||
|     { | ||||
|       "characterID": 14, | ||||
|       "name": "Starlord", | ||||
|       "HP": 104, | ||||
|       "MP": 8, | ||||
|       "AP": 3, | ||||
|       "meleeDamage": 9, | ||||
|       "rangeCombatDamage": 3, | ||||
|       "rangeCombatReach": 13 | ||||
|     }, | ||||
|     { | ||||
|       "characterID": 15, | ||||
|       "name": "Gamora", | ||||
|       "HP": 94, | ||||
|       "MP": 8, | ||||
|       "AP": 8, | ||||
|       "meleeDamage": 7, | ||||
|       "rangeCombatDamage": 1, | ||||
|       "rangeCombatReach": 15 | ||||
|     }, | ||||
|     { | ||||
|       "characterID": 16, | ||||
|       "name": "Ant Man", | ||||
|       "HP": 71, | ||||
|       "MP": 5, | ||||
|       "AP": 6, | ||||
|       "meleeDamage": 7, | ||||
|       "rangeCombatDamage": 3, | ||||
|       "rangeCombatReach": 1 | ||||
|     }, | ||||
|     { | ||||
|       "characterID": 17, | ||||
|       "name": "Vision", | ||||
|       "HP": 66, | ||||
|       "MP": 8, | ||||
|       "AP": 4, | ||||
|       "meleeDamage": 11, | ||||
|       "rangeCombatDamage": 5, | ||||
|       "rangeCombatReach": 3 | ||||
|     }, | ||||
|     { | ||||
|       "characterID": 18, | ||||
|       "name": "Deadpool", | ||||
|       "HP": 118, | ||||
|       "MP": 1, | ||||
|       "AP": 21, | ||||
|       "meleeDamage": 3, | ||||
|       "rangeCombatDamage": 2, | ||||
|       "rangeCombatReach": 8 | ||||
|     }, | ||||
|     { | ||||
|       "characterID": 19, | ||||
|       "name": "Loki", | ||||
|       "HP": 142, | ||||
|       "MP": 1, | ||||
|       "AP": 11, | ||||
|       "meleeDamage": 2, | ||||
|       "rangeCombatDamage": 1, | ||||
|       "rangeCombatReach": 7 | ||||
|     }, | ||||
|     { | ||||
|       "characterID": 20, | ||||
|       "name": "Silver Surfer", | ||||
|       "HP": 79, | ||||
|       "MP": 10, | ||||
|       "AP": 5, | ||||
|       "meleeDamage": 2, | ||||
|       "rangeCombatDamage": 1, | ||||
|       "rangeCombatReach": 17 | ||||
|     }, | ||||
|     { | ||||
|       "characterID": 21, | ||||
|       "name": "Mantis", | ||||
|       "HP": 149, | ||||
|       "MP": 3, | ||||
|       "AP": 10, | ||||
|       "meleeDamage": 9, | ||||
|       "rangeCombatDamage": 4, | ||||
|       "rangeCombatReach": 5 | ||||
|     }, | ||||
|     { | ||||
|       "characterID": 22, | ||||
|       "name": "Ghost Rider", | ||||
|       "HP": 74, | ||||
|       "MP": 15, | ||||
|       "AP": 5, | ||||
|       "meleeDamage": 12, | ||||
|       "rangeCombatDamage": 4, | ||||
|       "rangeCombatReach": 4 | ||||
|     }, | ||||
|     { | ||||
|       "characterID": 23, | ||||
|       "name": "Jessica Jones", | ||||
|       "HP": 92, | ||||
|       "MP": 10, | ||||
|       "AP": 1, | ||||
|       "meleeDamage": 15, | ||||
|       "rangeCombatDamage": 2, | ||||
|       "rangeCombatReach": 7 | ||||
|     }, | ||||
|     { | ||||
|       "characterID": 24, | ||||
|       "name": "Scarlet Witch", | ||||
|       "HP": 51, | ||||
|       "MP": 9, | ||||
|       "AP": 1, | ||||
|       "meleeDamage": 14, | ||||
|       "rangeCombatDamage": 1, | ||||
|       "rangeCombatReach": 11 | ||||
|     } | ||||
|   ] | ||||
| } | ||||
							
								
								
									
										15
									
								
								configs/gameConfig.game.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								configs/gameConfig.game.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | ||||
| { | ||||
|   "maxRounds": 18, | ||||
|   "maxRoundTime": 384, | ||||
|   "maxGameTime": 1347, | ||||
|   "maxAnimationTime": 260, | ||||
|   "spaceStoneCD": 5, | ||||
|   "mindStoneCD": 2, | ||||
|   "realityStoneCD": 5, | ||||
|   "powerStoneCD": 2, | ||||
|   "timeStoneCD": 6, | ||||
|   "soulStoneCD": 1, | ||||
|   "mindStoneDMG": 29, | ||||
|   "maxPauseTime": 698, | ||||
|   "maxResponseTime": 142 | ||||
| } | ||||
							
								
								
									
										12
									
								
								configs/newAsgard.scenario.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								configs/newAsgard.scenario.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| { | ||||
|     "scenario":[ | ||||
|         ["GRASS","GRASS", "GRASS", "GRASS", "GRASS", "GRASS", "GRASS"], | ||||
|         ["GRASS","PORTAL", "GRASS", "ROCK", "GRASS", "PORTAL", "GRASS"], | ||||
|         ["GRASS","GRASS", "GRASS", "ROCK", "GRASS", "GRASS", "GRASS"], | ||||
|         ["GRASS","GRASS", "GRASS", "ROCK", "GRASS", "GRASS", "GRASS"], | ||||
|         ["GRASS","GRASS", "ROCK", "GRASS", "ROCK", "GRASS", "GRASS"], | ||||
|         ["GRASS","ROCK", "ROCK", "GRASS", "GRASS", "ROCK", "GRASS"] | ||||
|     ], | ||||
|     "author": "jakobmh", | ||||
|     "name": "asgard" | ||||
| } | ||||
							
								
								
									
										2606
									
								
								configs/scenarioConfig.scenario.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2606
									
								
								configs/scenarioConfig.scenario.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										174
									
								
								configs/testmapwithportals.scenario.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								configs/testmapwithportals.scenario.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,174 @@ | ||||
| { | ||||
|   "scenario": [ | ||||
|     [ | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "ROCK", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS" | ||||
|     ], | ||||
|     [ | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "ROCK", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "PORTAL", | ||||
|       "GRASS", | ||||
|       "GRASS" | ||||
|     ], | ||||
|     [ | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "ROCK", | ||||
|       "ROCK", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "ROCK" | ||||
|     ], | ||||
|     [ | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "ROCK", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "ROCK", | ||||
|       "ROCK" | ||||
|     ], | ||||
|     [ | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "ROCK", | ||||
|       "ROCK", | ||||
|       "ROCK", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "ROCK" | ||||
|     ], | ||||
|     [ | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "ROCK", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS" | ||||
|     ], | ||||
|     [ | ||||
|       "GRASS", | ||||
|       "PORTAL", | ||||
|       "ROCK", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS" | ||||
|     ], | ||||
|     [ | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "ROCK", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS" | ||||
|     ], | ||||
|     [ | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "ROCK", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS" | ||||
|     ], | ||||
|     [ | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "ROCK", | ||||
|       "ROCK", | ||||
|       "ROCK", | ||||
|       "ROCK", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS" | ||||
|     ], | ||||
|     [ | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "ROCK", | ||||
|       "ROCK", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "PORTAL", | ||||
|       "GRASS", | ||||
|       "GRASS" | ||||
|     ], | ||||
|     [ | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS", | ||||
|       "GRASS" | ||||
|     ] | ||||
|   ], | ||||
|   "name": "TestMapWithPortals", | ||||
|   "author": "Lenard Hermann" | ||||
| } | ||||
| @ -45,7 +45,7 @@ try { | ||||
| 	java -jar Server/build/libs/Server.jar ` | ||||
| 	-c .\configs\marvelheros.character.json ` | ||||
| 	-m.\configs\matchconfig_1.game.json ` | ||||
| 	-s .\configs\asgard.scenario.json ` | ||||
| 	-s .\configs\newAsgard.scenario.json ` | ||||
| 	-v ` | ||||
| 	-p $port | ||||
| } catch { | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	