Server/Server/src/main/java/uulm/teamname/marvelous/server/net/ServerSession.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);
}
}