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 Server/build/libs/Server.jar Server.jar | ||||||
|  |  | ||||||
| COPY configs/asgard.scenario.json default/asgard.scenario.json | COPY configs/testmapwithportals.scenario.json default/testmapwithportals.scenario.json | ||||||
| COPY configs/marvelheros.character.json default/marvelheroes.character.json | COPY configs/characterConfig.character.json default/characterConfig.character.json | ||||||
| COPY configs/matchconfig_1.game.json default/matchconfig.game.json | COPY configs/gameConfig.game.json default/gameConfig.game.json | ||||||
|  |  | ||||||
| ARG MMU_LOG_LEVEL=3 | ARG MMU_LOG_LEVEL=3 | ||||||
|  |  | ||||||
| ARG MMU_CONF_MATCH=default/matchconfig.game.json | ARG MMU_CONF_MATCH=default/gameConfig.game.json | ||||||
| ARG MMU_CONF_CHARS=default/marvelheroes.character.json | ARG MMU_CONF_CHARS=default/characterConfig.character.json | ||||||
| ARG MMU_CONF_SCENARIO=default/asgard.scenario.json | ARG MMU_CONF_SCENARIO=default/testmapwithportals.scenario.json | ||||||
|  |  | ||||||
| ARG MMU_REPLAY_DIR=/replays | 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 { | jar { | ||||||
|     manifest { |     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.JSON; | ||||||
| import uulm.teamname.marvelous.gamelibrary.json.ValidationUtility; | import uulm.teamname.marvelous.gamelibrary.json.ValidationUtility; | ||||||
| import uulm.teamname.marvelous.server.args.ServerArgs; | 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.io.*; | ||||||
| import java.net.InetSocketAddress; | import java.net.InetSocketAddress; | ||||||
| @ -25,20 +26,22 @@ import java.util.Map; | |||||||
|  * {@code -c .\configs\marvelheros.character.json -m .\configs\matchconfig_1.game.json |  * {@code -c .\configs\marvelheros.character.json -m .\configs\matchconfig_1.game.json | ||||||
|  * -s .\configs\asgard.scenario.json -v} into the arguments field. |  * -s .\configs\asgard.scenario.json -v} into the arguments field. | ||||||
|  */ |  */ | ||||||
| public class Server { | public class ServerApplication { | ||||||
|     private static PartyConfig partyConfig; |     private static PartyConfig partyConfig; | ||||||
|     private static ScenarioConfig scenarioConfig; |     private static ScenarioConfig scenarioConfig; | ||||||
|     private static CharacterConfig characterConfig; |     private static CharacterConfig characterConfig; | ||||||
| 
 | 
 | ||||||
|     private static Integer maxLobbies; |     private static ServerSession session; | ||||||
| 
 | 
 | ||||||
|     public static void main(String[] args) { |     public static void main(String[] args) { | ||||||
|  |         Thread.currentThread().setName("Main"); | ||||||
|  | 
 | ||||||
|         ServerArgs serverArgs = new ServerArgs(); |         ServerArgs serverArgs = new ServerArgs(); | ||||||
|         JCommander jc = JCommander.newBuilder().addObject(serverArgs).build(); |         JCommander jc = JCommander.newBuilder().addObject(serverArgs).build(); | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             jc.parse(args); |             jc.parse(args); | ||||||
|         } catch (ParameterException e) { |         }catch(ParameterException e) { | ||||||
|             Logger.error("Invalid parameters: {}", e.getMessage()); |             Logger.error("Invalid parameters: {}", e.getMessage()); | ||||||
|             System.exit(1); |             System.exit(1); | ||||||
|             return; |             return; | ||||||
| @ -50,13 +53,11 @@ public class Server { | |||||||
|             return; |             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 |             // If checkConfig, the LogLevel is also set to max, because more information | ||||||
|             // is exactly what checking the requirements means |             // is exactly what checking the requirements means | ||||||
|             setLogLevel(5); |             setLogLevel(5); | ||||||
|         } else { |         }else { | ||||||
|             setLogLevel(serverArgs.getLogLevel()); |             setLogLevel(serverArgs.getLogLevel()); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -72,25 +73,33 @@ public class Server { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         Logger.info("populating static Server variables with config objects"); |         Logger.info("populating static Server variables with config objects"); | ||||||
|         Server.scenarioConfig = scenarioConfig; |         ServerApplication.scenarioConfig = scenarioConfig; | ||||||
|         Server.characterConfig = characterConfig; |         ServerApplication.characterConfig = characterConfig; | ||||||
|         Server.partyConfig = partyConfig; |         ServerApplication.partyConfig = partyConfig; | ||||||
| 
 | 
 | ||||||
|         InetSocketAddress address = new InetSocketAddress(serverArgs.getPort()); |         InetSocketAddress address = new InetSocketAddress(serverArgs.getPort()); | ||||||
|         Logger.trace("Inet address {} created", address); |         Logger.trace("Inet address {} created", address); | ||||||
| 
 | 
 | ||||||
|  |         Logger.trace("Instantiating ServerSession..."); | ||||||
|  |         session = new ServerSession(); | ||||||
|  | 
 | ||||||
|         Logger.trace("Instantiating MarvelousServer..."); |         Logger.trace("Instantiating MarvelousServer..."); | ||||||
|         MarvelousServer netConnector = new MarvelousServer(address); |         MarvelousServer server = new MarvelousServer(address); | ||||||
| 
 | 
 | ||||||
|         Logger.trace("Starting MarvelousServer..."); |         Logger.trace("Starting MarvelousServer..."); | ||||||
|         netConnector.start(); |         server.start(); | ||||||
|  | 
 | ||||||
|  |         Logger.trace("Starting ServerSession..."); | ||||||
|  |         getSession().run(); | ||||||
| 
 | 
 | ||||||
|         Logger.trace("End of Main reached. Exiting main thread."); |         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> . |      * It has to be executed <b>BEFORE ANY LOGGING OPERATIONS</b> . | ||||||
|      */ |      */ | ||||||
|  |     @SuppressWarnings("DuplicateBranchesInSwitch") | ||||||
|     private static void setLogLevel(int logLevel) { |     private static void setLogLevel(int logLevel) { | ||||||
|         Map<String, String> map = new HashMap<>(); |         Map<String, String> map = new HashMap<>(); | ||||||
| 
 | 
 | ||||||
| @ -144,18 +153,21 @@ public class Server { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         int grassFields = 0; |         int grassFields = 0; | ||||||
| 
 |         int portals = 0; | ||||||
|         for (FieldType[] row: config.get().scenario) { |         for (FieldType[] row: config.get().scenario) { | ||||||
|             for (FieldType type: row) { |             for (FieldType type: row) { | ||||||
|                 if (type == FieldType.GRASS) grassFields++; |                 if (type == FieldType.GRASS) grassFields++; | ||||||
|  |                 if (type == FieldType.PORTAL) portals++; | ||||||
|             } |             } | ||||||
|             if (grassFields > 18) break; |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (grassFields <= 18) { |         if (grassFields < 20) { | ||||||
|             Logger.error( |             Logger.error("Scenario Configuration vas invalid: Only {} grass fields found, which is less than 20", grassFields); | ||||||
|                     "Scenario Configuration vas invalid: Only {} grass fields found, which is less than 18" |             System.exit(1); | ||||||
|                     , grassFields); |         } | ||||||
|  | 
 | ||||||
|  |         if (portals < 2) { | ||||||
|  |             Logger.error("Scenario Configuration vas invalid: Only {} portals found, which is less than 2", portals); | ||||||
|             System.exit(1); |             System.exit(1); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -231,8 +243,7 @@ public class Server { | |||||||
|         return characterConfig; |         return characterConfig; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** Returns the maximum amount of lobbies the server should create */ |     public static ServerSession getSession() { | ||||||
|     public static Integer getMaxLobbies() { |         return session; | ||||||
|         return maxLobbies; |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -4,6 +4,7 @@ import com.beust.jcommander.Parameter; | |||||||
|  |  | ||||||
| import java.io.File; | import java.io.File; | ||||||
|  |  | ||||||
|  | @SuppressWarnings({"FieldMayBeFinal", "unused"}) | ||||||
| public class ServerArgs { | public class ServerArgs { | ||||||
|     /** Whether help is requested */ |     /** Whether help is requested */ | ||||||
|     @Parameter(names = {"-h", "--help", "-?", "--?", "-H"}, help = true) |     @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") |     @Parameter(names = {"--replay", "-r"}, description = "Path for replay files to be saved at") | ||||||
|     private String folderPath; |     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 */ |     /** Whether help is requested */ | ||||||
|     public boolean isHelp() { |     public boolean isHelp() { | ||||||
|         return help; |         return help; | ||||||
| @ -89,11 +87,6 @@ public class ServerArgs { | |||||||
|         return folderPath; |         return folderPath; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** The maximum mount of lobbies automatically created */ |  | ||||||
|     public int getMaxLobbies() { |  | ||||||
|         return maxLobbies; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public String toString() { |     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 org.tinylog.Logger; | ||||||
| import uulm.teamname.marvelous.gamelibrary.events.Event; | import uulm.teamname.marvelous.gamelibrary.events.Event; | ||||||
| import uulm.teamname.marvelous.gamelibrary.gamelogic.GameInstance; | import uulm.teamname.marvelous.gamelibrary.gamelogic.GameInstance; | ||||||
| import uulm.teamname.marvelous.gamelibrary.requests.Request; | import uulm.teamname.marvelous.gamelibrary.requests.Request; | ||||||
|  | import uulm.teamname.marvelous.server.game.GameSession; | ||||||
| 
 | 
 | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.concurrent.atomic.AtomicBoolean; | import java.util.concurrent.atomic.AtomicBoolean; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * The {@link GameLogicSegment} handles all {@link GameInstance game} relevant {@link Request Requests}. Therefore it |  * The {@link GameLogicSegment} handles all {@link GameInstance game} relevant {@link Request Requests}. | ||||||
|  * updates the game with the matching request. |  | ||||||
|  */ |  */ | ||||||
| public class GameLogicSegment implements Segment { | public class GameLogicSegment implements Segment { | ||||||
|     private GameInstance game; |  | ||||||
| 
 | 
 | ||||||
|  |     private final GameSession parent; | ||||||
| 
 | 
 | ||||||
|     public GameLogicSegment(GameInstance game) { |     public GameLogicSegment(GameSession parent) { | ||||||
|         this.game = game; |         this.parent = parent; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void processRequests(Packet packet, List<Event> carrier, AtomicBoolean abort) { |     public void processRequests(Packet packet, List<Event> carrier, AtomicBoolean abort) { | ||||||
|         Logger.trace("GameStateSegment received {} requests.", packet.size()); |         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)); |         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."); |             Logger.trace("Result from GameLogic is present. Adding requests to carrier."); | ||||||
|             carrier.addAll(result.get()); |             carrier.addAll(result.get()); | ||||||
|             packet.clear(); |             packet.clear(); | ||||||
|         } else { |         }else { | ||||||
|             Logger.debug("Result from GameLogic is invalid. Triggering error."); |             Logger.debug("Result from GameLogic is invalid. Triggering error."); | ||||||
|             abort.set(true); |             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.Request; | ||||||
| import uulm.teamname.marvelous.gamelibrary.requests.RequestType; | import uulm.teamname.marvelous.gamelibrary.requests.RequestType; | ||||||
| import uulm.teamname.marvelous.server.lobbymanager.Participant; | import uulm.teamname.marvelous.server.net.Client; | ||||||
| 
 | 
 | ||||||
| import java.util.*; | 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. |  * remove {@link Request Requests} at will. | ||||||
|  */ |  */ | ||||||
| public class Packet extends ArrayList<Request> { | 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; |         this.origin = origin; | ||||||
|         addAll(Arrays.asList(requests)); |         addAll(Arrays.asList(requests)); | ||||||
|     } |     } | ||||||
| @ -55,7 +55,7 @@ public class Packet extends ArrayList<Request> { | |||||||
|         this.removeIf(request -> !listOfTypes.contains(request.type)); |         this.removeIf(request -> !listOfTypes.contains(request.type)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public Participant getOrigin() { |     public Client getOrigin() { | ||||||
|         return origin; |         return origin; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -69,6 +69,7 @@ public class Packet extends ArrayList<Request> { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|  |     @SuppressWarnings("MethodDoesntCallSuperMethod") | ||||||
|     public Object clone() { |     public Object clone() { | ||||||
|         return new Packet(this.toArray(new Request[0]), this.origin); |         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 org.tinylog.Logger; | ||||||
| import uulm.teamname.marvelous.gamelibrary.events.Event; | 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.messages.ParticipantType; | ||||||
| import uulm.teamname.marvelous.gamelibrary.requests.RequestBuilder; | import uulm.teamname.marvelous.gamelibrary.requests.RequestBuilder; | ||||||
| import uulm.teamname.marvelous.gamelibrary.requests.RequestType; | import uulm.teamname.marvelous.gamelibrary.requests.RequestType; | ||||||
|  | import uulm.teamname.marvelous.server.game.GameSession; | ||||||
| 
 | 
 | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.concurrent.atomic.AtomicBoolean; | import java.util.concurrent.atomic.AtomicBoolean; | ||||||
| @ -16,35 +17,12 @@ import java.util.concurrent.atomic.AtomicBoolean; | |||||||
|  */ |  */ | ||||||
| public class PauseSegment implements Segment { | public class PauseSegment implements Segment { | ||||||
| 
 | 
 | ||||||
|     private boolean paused; |     private final GameSession parent; | ||||||
| 
 | 
 | ||||||
|     public PauseSegment() { |     public PauseSegment(GameSession parent) { | ||||||
|         paused = false; |         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 |      * Pipelining method to process a set of requests. The list of requests will be processed according to the following | ||||||
|      * rules: |      * rules: | ||||||
| @ -68,47 +46,46 @@ public class PauseSegment implements Segment { | |||||||
|     public void processRequests(Packet packet, List<Event> carrier, AtomicBoolean abort) { |     public void processRequests(Packet packet, List<Event> carrier, AtomicBoolean abort) { | ||||||
|         Logger.trace("PauseSegment received {} requests. PausedState is {}", |         Logger.trace("PauseSegment received {} requests. PausedState is {}", | ||||||
|                 packet.size(), |                 packet.size(), | ||||||
|                 paused); |                 parent.getPaused()); | ||||||
|  | 
 | ||||||
|         // check if there is a pause request (either start or stop) |         // 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"); |             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"); |                 Logger.trace("Invalid pause start request. Aborting"); | ||||||
|                 abort.set(true); |                 abort.set(true); | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|             if (!paused) { |             if(!parent.getPaused()) { | ||||||
|                 // pause the game |                 parent.setPaused(true); | ||||||
|                 pauseGame(); |  | ||||||
|                 // create a new PauseStartEvent |                 // create a new PauseStartEvent | ||||||
|                 carrier.add(new EventBuilder(EventType.PauseStartEvent).buildGameEvent()); |                 carrier.add(new EventBuilder(EventType.PauseStartEvent).buildGameEvent()); | ||||||
|                 Logger.trace("Added PauseStartEvent to pipeline carrier"); |                 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."); |                 Logger.info("PauseStartRequest sent even though the game wasn't paused. Error triggered."); | ||||||
|                 abort.set(true); |                 abort.set(true); | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|         } else if (packet.contains(new RequestBuilder(RequestType.PauseStopRequest).buildGameRequest())) { |         }else if(packet.contains(new RequestBuilder(RequestType.PauseStopRequest).buildGameRequest())) { | ||||||
|             Logger.trace("PauseStopRequest found"); |             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"); |                 Logger.trace("Invalid pause stop request. Aborting"); | ||||||
|                 abort.set(true); |                 abort.set(true); | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|             if (paused) { |             if(parent.getPaused()) { | ||||||
|                 pauseEnd(); |                 parent.setPaused(false); | ||||||
|                 Logger.debug("Game unpaused."); |  | ||||||
|                 // create a new PauseStartRequest |                 // create a new PauseStartRequest | ||||||
|                 carrier.add(new EventBuilder(EventType.PauseStopEvent).buildGameEvent()); |                 carrier.add(new EventBuilder(EventType.PauseStopEvent).buildGameEvent()); | ||||||
|                 Logger.trace("Added PauseStopEvent to pipeline carrier"); |                 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."); |                 Logger.info("PauseStopRequest sent even though the game wasn't paused. Error triggered."); | ||||||
|                 abort.set(true); |                 abort.set(true); | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (paused) { |         if(parent.getPaused()) { | ||||||
|             Logger.trace("As the game is paused, Requests are removed."); |             Logger.trace("As the game is paused, Requests are removed."); | ||||||
|             packet.removeRequestsOfTypes( |             packet.removeRequestsOfTypes( | ||||||
|                     RequestType.MeleeAttackRequest, |                     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 org.tinylog.Logger; | ||||||
| import uulm.teamname.marvelous.gamelibrary.events.Event; | import uulm.teamname.marvelous.gamelibrary.events.Event; | ||||||
| import uulm.teamname.marvelous.gamelibrary.requests.Request; | 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.*; | ||||||
| import java.util.concurrent.atomic.AtomicBoolean; | 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 |      * first check whether the {@link Optional} is empty by doing {@link Optional#isEmpty()} or {@link | ||||||
|      * Optional#isPresent()}, and act accordingly. |      * 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"); |         Logger.trace("Pipeline started RequestProcessing"); | ||||||
|         // The packet carries the requests, and gets smaller per segment |         // The packet carries the requests, and gets smaller per segment | ||||||
|         Packet packet = new Packet(requests, origin); |         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 org.tinylog.Logger; | ||||||
| import uulm.teamname.marvelous.gamelibrary.entities.EntityType; | import uulm.teamname.marvelous.gamelibrary.entities.EntityType; | ||||||
| import uulm.teamname.marvelous.gamelibrary.events.Event; | import uulm.teamname.marvelous.gamelibrary.events.Event; | ||||||
| import uulm.teamname.marvelous.gamelibrary.requests.CharacterRequest; | import uulm.teamname.marvelous.gamelibrary.requests.CharacterRequest; | ||||||
| import uulm.teamname.marvelous.gamelibrary.requests.Request; | 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.List; | ||||||
| import java.util.concurrent.atomic.AtomicBoolean; | import java.util.concurrent.atomic.AtomicBoolean; | ||||||
| @ -14,8 +14,9 @@ public class PlayerFilterSegment implements Segment { | |||||||
|     @Override |     @Override | ||||||
|     public void processRequests(Packet packet, List<Event> carrier, AtomicBoolean abort) { |     public void processRequests(Packet packet, List<Event> carrier, AtomicBoolean abort) { | ||||||
|         Logger.trace("PlayerFilterSegment received {} requests", packet.size()); |         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, |                 case MeleeAttackRequest, | ||||||
|                         RangedAttackRequest, |                         RangedAttackRequest, | ||||||
|                         MoveRequest, |                         MoveRequest, | ||||||
| @ -23,7 +24,7 @@ public class PlayerFilterSegment implements Segment { | |||||||
|                         UseInfinityStoneRequest -> isValid((CharacterRequest) request, packet.getOrigin()); |                         UseInfinityStoneRequest -> isValid((CharacterRequest) request, packet.getOrigin()); | ||||||
|                 default -> true; |                 default -> true; | ||||||
|             }; |             }; | ||||||
|             if (!valid) { |             if(!valid) { | ||||||
|                 Logger.debug("Invalid request of type {} of enemy player found, setting abort flag", request.type); |                 Logger.debug("Invalid request of type {} of enemy player found, setting abort flag", request.type); | ||||||
|                 abort.set(true); |                 abort.set(true); | ||||||
|                 break; |                 break; | ||||||
| @ -31,21 +32,17 @@ public class PlayerFilterSegment implements Segment { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private boolean isValid(CharacterRequest request, Participant participant) { |     private boolean isValid(CharacterRequest request, Client client) { | ||||||
|         EntityType type = switch(participant.type) { |         EntityType type = switch(client.getType()) { | ||||||
|             case PlayerOne -> EntityType.P1; |             case PlayerOne -> EntityType.P1; | ||||||
|             case PlayerTwo -> EntityType.P2; |             case PlayerTwo -> EntityType.P2; | ||||||
|             case Spectator -> null; |             case Spectator -> null; | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         if (type == null) { |         if(type == null) { | ||||||
|             Logger.warn("Some spectator-sent movement requests arrived in the PlayerFilterSegment.\n" + |  | ||||||
|                     "Have you ordered your segments properly?"); |  | ||||||
|             return false; |             return false; | ||||||
|         } else { |         }else { | ||||||
|             return request.originEntity.type.equals(type); |             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 org.tinylog.Logger; | ||||||
| import uulm.teamname.marvelous.gamelibrary.events.Event; | import uulm.teamname.marvelous.gamelibrary.events.Event; | ||||||
| import uulm.teamname.marvelous.gamelibrary.gamelogic.GameInstance; | import uulm.teamname.marvelous.gamelibrary.gamelogic.GameInstance; | ||||||
| import uulm.teamname.marvelous.gamelibrary.messages.server.EventMessage; | import uulm.teamname.marvelous.gamelibrary.messages.server.EventMessage; | ||||||
| import uulm.teamname.marvelous.gamelibrary.requests.RequestType; | import uulm.teamname.marvelous.gamelibrary.requests.RequestType; | ||||||
|  | import uulm.teamname.marvelous.server.game.GameSession; | ||||||
| 
 | 
 | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.concurrent.atomic.AtomicBoolean; | import java.util.concurrent.atomic.AtomicBoolean; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * The {@link RequestGameStateSegment} handles requests of {@link RequestType} Req. Therefore it sends the active |  * The {@link RequestGameStateSegment} handles requests of {@link RequestType} Req. | ||||||
|  * gamestate and clears the {@link Packet} afterwards. |  | ||||||
|  */ |  */ | ||||||
| public class RequestGameStateSegment implements Segment { | public class RequestGameStateSegment implements Segment { | ||||||
| 
 | 
 | ||||||
|     private final GameInstance game; |     private final GameSession parent; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     public RequestGameStateSegment(GameInstance game) { |     public RequestGameStateSegment(GameSession parent) { | ||||||
|         this.game = game; |         this.parent = parent; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void processRequests(Packet packet, List<Event> carrier, AtomicBoolean abort) { |     public void processRequests(Packet packet, List<Event> carrier, AtomicBoolean abort) { | ||||||
|         Logger.trace("RequestGameStateSegment received {} requests", packet.size()); |         Logger.trace("RequestGameStateSegment received {} requests", packet.size()); | ||||||
|         if (packet.containsRequestOfType(RequestType.Req)) { | 
 | ||||||
|             Logger.trace("Req event found. Returning Gamestate, and clearing entire RequestList"); |         if(packet.containsRequestOfType(RequestType.Req)) { | ||||||
|             var gamestateEventMessage = new EventMessage(); |             Logger.trace("Req event found. Returning GameState, and clearing entire RequestList"); | ||||||
|             gamestateEventMessage.messages = new Event[] {game.getGameStateEvent()}; |             EventMessage message = new EventMessage(); | ||||||
|             packet.getOrigin().sendMessage(gamestateEventMessage); |             message.messages = new Event[] {parent.getInstance().getGameStateEvent()}; | ||||||
|  |             packet.getOrigin().sendMessage(message); | ||||||
|             carrier.clear(); |             carrier.clear(); | ||||||
|             packet.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.events.Event; | ||||||
| import uulm.teamname.marvelous.gamelibrary.requests.Request; | 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 |      *                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;} |      *                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 { | public enum ClientState { | ||||||
|     Blank, |     Blank, | ||||||
|     Ready, |     Ready, | ||||||
|     Assigned, |     Assigned, | ||||||
|  |     Selected, | ||||||
|     Reconnect, |     Reconnect, | ||||||
|     Playing |     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; | 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; | package uulm.teamname.marvelous.server; | ||||||
|  |  | ||||||
| import uulm.teamname.marvelous.gamelibrary.config.*; | 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.ArrayList; | ||||||
| import java.util.Iterator; | import java.util.Iterator; | ||||||
| import java.util.concurrent.ThreadLocalRandom; | import java.util.concurrent.ThreadLocalRandom; | ||||||
| @ -86,16 +84,4 @@ public class BaseGameLogicTest { | |||||||
|  |  | ||||||
|         return props; |         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; | package uulm.teamname.marvelous.server; | ||||||
| 
 | 
 | ||||||
| import org.java_websocket.WebSocket; |  | ||||||
| import org.junit.jupiter.api.*; | import org.junit.jupiter.api.*; | ||||||
| import org.mockito.MockedStatic; | import org.mockito.MockedStatic; | ||||||
| import org.mockito.Mockito; | 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.PlayerReadyMessage; | ||||||
| import uulm.teamname.marvelous.gamelibrary.messages.client.ReconnectMessage; | import uulm.teamname.marvelous.gamelibrary.messages.client.ReconnectMessage; | ||||||
| import uulm.teamname.marvelous.gamelibrary.messages.server.*; | import uulm.teamname.marvelous.gamelibrary.messages.server.*; | ||||||
| import uulm.teamname.marvelous.server.lobbymanager.LobbyConnection; | import uulm.teamname.marvelous.server.net.Client; | ||||||
| import uulm.teamname.marvelous.server.lobbymanager.LobbyManager; | import uulm.teamname.marvelous.server.net.ServerSession; | ||||||
| import uulm.teamname.marvelous.server.lobbymanager.LobbyRunner; | import uulm.teamname.marvelous.server.net.SocketEvent; | ||||||
| import uulm.teamname.marvelous.server.netconnector.UserManager; | import uulm.teamname.marvelous.server.net.SocketEventType; | ||||||
| 
 | 
 | ||||||
| import java.lang.reflect.Constructor; |  | ||||||
| import java.lang.reflect.Field; | import java.lang.reflect.Field; | ||||||
| import java.lang.reflect.InvocationTargetException; |  | ||||||
| import java.util.HashMap; |  | ||||||
| import java.util.Map; |  | ||||||
| import java.util.Optional; | import java.util.Optional; | ||||||
| 
 | 
 | ||||||
| import static org.mockito.Mockito.*; | import static org.mockito.Mockito.*; | ||||||
| 
 | 
 | ||||||
| class MarvelousServerTest extends BaseGameLogicTest { | @SuppressWarnings({"ResultOfMethodCallIgnored", "SameParameterValue", "unchecked"}) | ||||||
|     private static MockedStatic<Server> serverMock; | class MarvelousServerApplicationTest extends BaseGameLogicTest { | ||||||
|  |     private static ServerSession session; | ||||||
|  |     private static MockedStatic<ServerApplication> serverMock; | ||||||
|     private static JSON json; |     private static JSON json; | ||||||
| 
 | 
 | ||||||
|     @BeforeAll |     @BeforeAll | ||||||
|     static void start() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException { |     static void start() { | ||||||
|         Constructor<UserManager> um = UserManager.class.getDeclaredConstructor(); |         session = new ServerSession(); | ||||||
|         um.setAccessible(true); |  | ||||||
|         um.newInstance(); |  | ||||||
|         Constructor<LobbyManager> lm = LobbyManager.class.getDeclaredConstructor(); |  | ||||||
|         lm.setAccessible(true); |  | ||||||
|         lm.newInstance(); |  | ||||||
| 
 | 
 | ||||||
|         serverMock = Mockito.mockStatic(Server.class); |         serverMock = Mockito.mockStatic(ServerApplication.class); | ||||||
| 
 | 
 | ||||||
|         generate(); |         generate(); | ||||||
| 
 | 
 | ||||||
|         setPrivateFinalBoolean(LobbyConnection.class, "synchronous", true); |         serverMock.when(ServerApplication::getSession).thenReturn(session); | ||||||
|         setPrivateFinalBoolean(LobbyRunner.class, "synchronous", true); |         serverMock.when(ServerApplication::getPartyConfig).thenReturn(partyConfig); | ||||||
| 
 |         serverMock.when(ServerApplication::getScenarioConfig).thenReturn(scenarioConfig); | ||||||
|         serverMock.when(Server::getMaxLobbies).thenReturn(10); |         serverMock.when(ServerApplication::getCharacterConfig).thenReturn(characterConfig); | ||||||
|         serverMock.when(Server::getPartyConfig).thenReturn(partyConfig); |  | ||||||
|         serverMock.when(Server::getScenarioConfig).thenReturn(scenarioConfig); |  | ||||||
|         serverMock.when(Server::getCharacterConfig).thenReturn(characterConfig); |  | ||||||
| 
 | 
 | ||||||
|         json = new JSON(characterConfig); |         json = new JSON(characterConfig); | ||||||
| 
 | 
 | ||||||
|         if (!Configuration.isFrozen()) { |         if(!Configuration.isFrozen()) { | ||||||
|             Configuration.set("writer1", "console"); |             Configuration.set("writer1", "console"); | ||||||
|             Configuration.set("writer1.level", "trace"); |             Configuration.set("writer1.level", "trace"); | ||||||
|             Configuration.set("writer1.format", "[{thread}] {level}: <TEST> {message}"); |             Configuration.set("writer1.format", "[{thread}] {level}: <TEST> {message}"); | ||||||
|         } |         } | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     @Test |     @Test | ||||||
|  |     @Disabled | ||||||
|     void main() { |     void main() { | ||||||
|         UserManager m = UserManager.getInstance(); |         session.run(); | ||||||
| 
 | 
 | ||||||
|         WebSocket p1 = mock(WebSocket.class); |         Client p1 = mock(Client.class); | ||||||
|         WebSocket p2 = mock(WebSocket.class); |         Client p2 = mock(Client.class); | ||||||
|         WebSocket s1 = mock(WebSocket.class); |         Client s1 = mock(Client.class); | ||||||
|         WebSocket s2 = mock(WebSocket.class); |         Client s2 = mock(Client.class); | ||||||
| 
 | 
 | ||||||
|         when(p1.getResourceDescriptor()).thenReturn("/"); |         session.addEvent(new SocketEvent(SocketEventType.Connect, p1)); | ||||||
|         when(p2.getResourceDescriptor()).thenReturn("/"); |         ensureHandshake(p1, "Player 1", "1234", false); | ||||||
|         when(s1.getResourceDescriptor()).thenReturn("/"); |  | ||||||
|         when(s2.getResourceDescriptor()).thenReturn("/"); |  | ||||||
| 
 | 
 | ||||||
|         m.connectUser(p1); |         session.addEvent(new SocketEvent(SocketEventType.Connect, p2)); | ||||||
|         ensureHandshake(m, p1, "Player 1", "1234", false); |         ensureHandshake(p2, "Player 2", "4321", false); | ||||||
| 
 | 
 | ||||||
|         m.connectUser(p2); |         session.addEvent(new SocketEvent(SocketEventType.Connect, s1)); | ||||||
|         ensureHandshake(m, p2, "Player 2", "4321", false); |         ensureHandshake(s1, "Spectator 1", "3333", false); | ||||||
| 
 | 
 | ||||||
|         m.connectUser(s1); |         session.addEvent(new SocketEvent(SocketEventType.Connect, s2)); | ||||||
|         ensureHandshake(m, s1, "Spectator 1", "3333", false); |         ensureHandshake(s2, "Spectator 2", "4444", false); | ||||||
| 
 | 
 | ||||||
|         m.connectUser(s2); |         ensurePlayerReady(p1, true, RoleEnum.PLAYER); | ||||||
|         ensureHandshake(m, s2, "Spectator 2", "4444", false); |         ensurePlayerReady(p2, true, RoleEnum.PLAYER); | ||||||
|  |         ensureSpectatorReady(s1, true, RoleEnum.SPECTATOR); | ||||||
| 
 | 
 | ||||||
|         ensurePlayerReady(m, p1, true, RoleEnum.PLAYER); |         ensureCharacterSelection(p1, true); | ||||||
|         ensurePlayerReady(m, p2, true, RoleEnum.PLAYER); |         ensureCharacterSelection(p2, false); | ||||||
|         ensureSpectatorReady(m, s1, true, RoleEnum.SPECTATOR); |  | ||||||
| 
 |  | ||||||
|         ensureCharacterSelection(m, p1, true); |  | ||||||
|         ensureCharacterSelection(m, p2, false); |  | ||||||
| 
 | 
 | ||||||
|         GameStructureMessage game = new GameStructureMessage(); |         GameStructureMessage game = new GameStructureMessage(); | ||||||
|         game.playerOneName = "Player 1"; |         game.playerOneName = "Player 1"; | ||||||
| @ -119,14 +103,14 @@ class MarvelousServerTest extends BaseGameLogicTest { | |||||||
| 
 | 
 | ||||||
|         clearInvocations(p1, p2, s1); |         clearInvocations(p1, p2, s1); | ||||||
| 
 | 
 | ||||||
|         m.disconnectUser(p1, true); |         session.addEvent(new SocketEvent(SocketEventType.Disconnect, p1)); | ||||||
| 
 | 
 | ||||||
|         m.connectUser(p1); |         session.addEvent(new SocketEvent(SocketEventType.Connect, p1)); | ||||||
|         ensureHandshake(m, p1, "Player 1", "1234", true); |         ensureHandshake(p1, "Player 1", "1234", true); | ||||||
| 
 | 
 | ||||||
|         ReconnectMessage message = new ReconnectMessage(); |         ReconnectMessage message = new ReconnectMessage(); | ||||||
|         message.reconnect = true; |         message.reconnect = true; | ||||||
|         sendMessage(m, p1, message); |         sendMessage(p1, message); | ||||||
| 
 | 
 | ||||||
|         ensureReceived(p1, new GeneralAssignmentMessage()); |         ensureReceived(p1, new GeneralAssignmentMessage()); | ||||||
|         ensureReceived(p1, new GameStructureMessage()); |         ensureReceived(p1, new GameStructureMessage()); | ||||||
| @ -134,7 +118,7 @@ class MarvelousServerTest extends BaseGameLogicTest { | |||||||
|         System.out.println("Test Completed"); |         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(); |         HelloServerMessage message = new HelloServerMessage(); | ||||||
|         message.name = name; |         message.name = name; | ||||||
|         message.deviceID = deviceID; |         message.deviceID = deviceID; | ||||||
| @ -142,10 +126,10 @@ class MarvelousServerTest extends BaseGameLogicTest { | |||||||
|         HelloClientMessage response = new HelloClientMessage(); |         HelloClientMessage response = new HelloClientMessage(); | ||||||
|         response.runningGame = runningGame; |         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(); |         PlayerReadyMessage message = new PlayerReadyMessage(); | ||||||
|         message.startGame = startGame; |         message.startGame = startGame; | ||||||
|         message.role = role; |         message.role = role; | ||||||
| @ -153,10 +137,10 @@ class MarvelousServerTest extends BaseGameLogicTest { | |||||||
|         GameAssignmentMessage response = new GameAssignmentMessage(); |         GameAssignmentMessage response = new GameAssignmentMessage(); | ||||||
|         //properties are left null because we can't test their content (they are random) |         //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(); |         PlayerReadyMessage message = new PlayerReadyMessage(); | ||||||
|         message.startGame = startGame; |         message.startGame = startGame; | ||||||
|         message.role = role; |         message.role = role; | ||||||
| @ -164,10 +148,10 @@ class MarvelousServerTest extends BaseGameLogicTest { | |||||||
|         GeneralAssignmentMessage response = new GeneralAssignmentMessage(); |         GeneralAssignmentMessage response = new GeneralAssignmentMessage(); | ||||||
|         //properties are left null because we can't test their content (they are random) |         //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(); |         CharacterSelectionMessage message = new CharacterSelectionMessage(); | ||||||
|         message.characters = new Boolean[12]; |         message.characters = new Boolean[12]; | ||||||
|         for(int i = 0; i < 6; i++) { |         for(int i = 0; i < 6; i++) { | ||||||
| @ -180,44 +164,44 @@ class MarvelousServerTest extends BaseGameLogicTest { | |||||||
|         if(confirmSelection) { |         if(confirmSelection) { | ||||||
|             ConfirmSelectionMessage response = new ConfirmSelectionMessage(); |             ConfirmSelectionMessage response = new ConfirmSelectionMessage(); | ||||||
|             response.selectionComplete = false; |             response.selectionComplete = false; | ||||||
|             ensureResponse(m, c, message, response); |             ensureResponse(c, message, response); | ||||||
|         } else { |         } else { | ||||||
|             sendMessage(m, c, message); |             sendMessage(c, message); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     /** Sends a message from the socket. */ |     /** 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); |         Optional<String> in = json.stringify(message); | ||||||
| 
 | 
 | ||||||
|         if(in.isPresent()) { |         if(in.isPresent()) { | ||||||
|             m.messageReceived(c, in.get()); |             session.addEvent(new SocketEvent(SocketEventType.Message, c, in.get())); | ||||||
|         }else { |         }else { | ||||||
|             throw new IllegalArgumentException("[TEST] Message in test call could not be serialized!"); |             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. */ |     /** 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); |         Optional<String> in = json.stringify(message); | ||||||
| 
 | 
 | ||||||
|         if(in.isPresent()) { |         if(in.isPresent()) { | ||||||
|             m.messageReceived(c, in.get()); |             session.addEvent(new SocketEvent(SocketEventType.Message, c, in.get())); | ||||||
|             verify(c).send((String)argThat( |             verify(c.getSocket()).send((String)argThat( | ||||||
|                     (out)->verifyResponse((String)out, response) |                     (out)->verifyResponse((String)out, response) | ||||||
|             )); |             )); | ||||||
|             clearInvocations(c); |             clearInvocations(c.getSocket()); | ||||||
|         }else { |         }else { | ||||||
|             throw new IllegalArgumentException("[TEST] Message in test call could not be serialized!"); |             throw new IllegalArgumentException("[TEST] Message in test call could not be serialized!"); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** Ensures that the given socket received the given response. */ |     /** 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; |         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)); |             Optional<BasicMessage> received = json.parse(i.getArgument(0, String.class)); | ||||||
| 
 | 
 | ||||||
|             if(received.isPresent()) { |             if(received.isPresent()) { | ||||||
| @ -227,7 +211,7 @@ class MarvelousServerTest extends BaseGameLogicTest { | |||||||
|                     continue; //message is not of the right type |                     continue; //message is not of the right type | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 T message = (T) parsed; |                 T message = (T)parsed; | ||||||
| 
 | 
 | ||||||
|                 for(Field field: response.getClass().getDeclaredFields()) { |                 for(Field field: response.getClass().getDeclaredFields()) { | ||||||
|                     try { |                     try { | ||||||
| @ -265,7 +249,7 @@ class MarvelousServerTest extends BaseGameLogicTest { | |||||||
|         Optional<BasicMessage> in = json.parse(actual); |         Optional<BasicMessage> in = json.parse(actual); | ||||||
| 
 | 
 | ||||||
|         if(in.isPresent()) { |         if(in.isPresent()) { | ||||||
|             T message = (T) in.get(); |             T message = (T)in.get(); | ||||||
| 
 | 
 | ||||||
|             for(Field field: expected.getClass().getDeclaredFields()) { |             for(Field field: expected.getClass().getDeclaredFields()) { | ||||||
|                 try { |                 try { | ||||||
| @ -290,6 +274,7 @@ class MarvelousServerTest extends BaseGameLogicTest { | |||||||
| 
 | 
 | ||||||
|     @AfterAll |     @AfterAll | ||||||
|     static void stop() { |     static void stop() { | ||||||
|  |         session.stop(); | ||||||
|         serverMock.close(); |         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 ` | 	java -jar Server/build/libs/Server.jar ` | ||||||
| 	-c .\configs\marvelheros.character.json ` | 	-c .\configs\marvelheros.character.json ` | ||||||
| 	-m.\configs\matchconfig_1.game.json ` | 	-m.\configs\matchconfig_1.game.json ` | ||||||
| 	-s .\configs\asgard.scenario.json ` | 	-s .\configs\newAsgard.scenario.json ` | ||||||
| 	-v ` | 	-v ` | ||||||
| 	-p $port | 	-p $port | ||||||
| } catch { | } catch { | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	