394 lines
12 KiB
Java
394 lines
12 KiB
Java
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);
|
|
}
|
|
}
|