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