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