diff --git a/Meilenstein III/Diary.txt b/Meilenstein III/Diary.txt index 4520955..41f6472 100644 --- a/Meilenstein III/Diary.txt +++ b/Meilenstein III/Diary.txt @@ -256,6 +256,10 @@ Stand 15:00 Uhr: Versuch meine commits wieder richtig zuorden zu können indem ich die user.name und user.email Variablen von git auf meinem Rechner neu configuriere. +Stand 17:30 Uhr: + Ich habe einen neuen Branch "BudaLobbySebastian" erstellt, worauf ich schaue wie und wo eigentlich eine "Lobby" + lebt. LISTL wurde auch implementiert aber noch nicht getestet. + ToDo: Spiellogik: - Send() methode von Passenger mit Client-Server verknüpfen(Seraina) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/Client.java b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/Client.java index 85a783f..2715d6b 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/Client.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/Client.java @@ -5,14 +5,13 @@ import ch.unibas.dmi.dbis.cs108.multiplayer.helpers.ClientPinger; import ch.unibas.dmi.dbis.cs108.multiplayer.helpers.Protocol; + import ch.unibas.dmi.dbis.cs108.multiplayer.server.JServerProtocolParser; import java.net.Socket; import java.io.*; import java.net.UnknownHostException; import java.util.Objects; import java.util.Scanner; -import java.util.Timer; -import java.util.TimerTask; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -31,6 +30,7 @@ public class Client { */ int position = Integer.MAX_VALUE; + public Client(Socket socket) { try { this.socket = socket; @@ -120,6 +120,7 @@ public class Client { try { chatMsg = in.readLine(); //todo: maybe if if (chatMsg != null) { + //LOGGER.debug("chatMSG recieved from Server: " + chatMsg); parse(chatMsg); } else { System.out.println("chatMsg is null"); throw new IOException();} } catch (IOException e) { @@ -208,6 +209,7 @@ public class Client { } + public Socket getSocket() { return socket; } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/Client_01.java b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/Client_01.java new file mode 100644 index 0000000..bd2e499 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/Client_01.java @@ -0,0 +1,8 @@ +package ch.unibas.dmi.dbis.cs108.multiplayer.client; + +public class Client_01 { + public static void main(String[] args) { + Client.main(args); + } + +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/Client_02.java b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/Client_02.java new file mode 100644 index 0000000..23c9122 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/Client_02.java @@ -0,0 +1,7 @@ +package ch.unibas.dmi.dbis.cs108.multiplayer.client; + +public class Client_02 { + public static void main(String[] args) { + Client.main(args); + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/JClientProtocolParser.java b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/JClientProtocolParser.java index 3af6d14..09f1799 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/JClientProtocolParser.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/JClientProtocolParser.java @@ -39,6 +39,10 @@ public class JClientProtocolParser { case Protocol.printToClientConsole: System.out.println(msg.substring(6)); break; + case Protocol.printToClientChat: + //todo: handle chat separately from console. + System.out.println(msg.substring(6)); + break; case Protocol.serverConfirmQuit: c.disconnectFromServer(); break; @@ -54,6 +58,7 @@ public class JClientProtocolParser { c.positionSetter(msg.substring(6)); //TODO(Seraina): How can be enforced, that clients won't vote otherwise? Trigger a methode here that listens to input break; + case Protocol.serverDeliversLobbyList: default: System.out.println("Received unknown command"); } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/MessageFormatter.java b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/MessageFormatter.java index 7679536..948e35f 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/MessageFormatter.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/MessageFormatter.java @@ -11,8 +11,8 @@ public class MessageFormatter { public static final BudaLogConfig l = new BudaLogConfig(LOGGER); /** - * Takes a given Message and reformats it to where the JServerProtocolParser.parse() method can - * handle it (see Protocol.java). May need to be redesigned once the games uses a GUI + * Takes a given client input and reformats it to where the JServerProtocolParser.parse() method can + * handle it (see Protocol.java). May need to be redesigned once the game uses a GUI. * * @param msg the Messaged to be reformatted * @return the reformatted message in the form HEADR$msg @@ -29,6 +29,14 @@ public class MessageFormatter { } switch (header) { case "/c": + stringBuilder.append(Protocol.chatMsgToLobby + "$"); + try { + s = msg.substring(3); + } catch (Exception e) { + System.out.println("You didn't even write a chat line, you silly billy!"); + } + break; + case "/b": stringBuilder.append(Protocol.chatMsgToAll + "$"); try { s = msg.substring(3); @@ -37,7 +45,7 @@ public class MessageFormatter { } break; case "/q": - stringBuilder.append(Protocol.clientQuitRequest + "$"); + stringBuilder.append(Protocol.clientQuitRequest); s = ""; break; case "/n": @@ -49,15 +57,30 @@ public class MessageFormatter { } break; case "/g": - //CRTGM command - stringBuilder.append(Protocol.createNewGame + "$"); - s = ""; //command has no parameters - //TODO add LOGGER msg. Find out if .info or .debug. + stringBuilder.append(Protocol.createNewLobby); break; case "/l": - //LISTL command - stringBuilder.append(Protocol.listLobbies + "$"); - s = ""; //Command has no parameters + stringBuilder.append(Protocol.listLobbies); + break; + case "/p": + stringBuilder.append(Protocol.listPlayersInLobby); + break; + case "/j": + stringBuilder.append(Protocol.joinLobby + "$"); + try { + s = msg.substring(3); + } catch (Exception ignored) { + } + break; + case "/w": + stringBuilder.append(Protocol.whisper + "$"); + try { + s = msg.substring(3); + } catch (Exception ignored) { + } + break; + case "/e": + stringBuilder.append(Protocol.leaveLobby); break; case "/v": try { diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/helpers/Protocol.java b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/helpers/Protocol.java index e40e3ff..1bc06ca 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/helpers/Protocol.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/helpers/Protocol.java @@ -1,5 +1,7 @@ package ch.unibas.dmi.dbis.cs108.multiplayer.helpers; +import ch.unibas.dmi.dbis.cs108.multiplayer.server.Lobby; + /** * This class is where the Protocol commands are saved as strings. The idea is that every class that * uses protocol messages does not directly use e.g. "CHATA" in the code but rather uses @@ -41,6 +43,15 @@ public class Protocol { */ public static final String chatMsgToAll = "CHATA"; + /** + * When the server receives this, it broadcasts a chat message to all clients in the same Lobby. + * The message has to be given in the protocol message after {@code CHATL$}, for example the protocol message {@code + * CHATL$Hello everybody!}, if sent from the user named Poirot, will print {@code Poirot: Hello + * everybody!} to the chat console of every client in the lobby (note the absence / presence of spaces). + * If the client is not in a lobby, the chat message will be sent to everyone not in a lobby. + */ + public static final String chatMsgToLobby = "CHATL"; + /** * The message sent by the client on login to set their name. For example, {@code LOGIN$Poirot} * will use the clientHandler.setUsernameOnLogin() method to set this client's username to Poirot, @@ -75,23 +86,41 @@ public class Protocol { public static final String clientQuitRequest = "QUITR"; /** - * TODO: enable for client - * TODO: add sever response - * Client sends this message when he wants to create a new game. + * Client sends this message when they want to create a new game. * Client issues this command in {@link ch.unibas.dmi.dbis.cs108.multiplayer.client.MessageFormatter} * using "/g". - * First a lobby {@link ch.unibas.dmi.dbis.cs108.sebaschi.Lobby} is created of which the requesting client is the admin of. + * First a lobby {@link Lobby} is created of which the requesting client is the admin of. */ - public static final String createNewGame = "CRTGM"; + public static final String createNewLobby = "CRTLB"; /** - * TODO: implement in {@link ch.unibas.dmi.dbis.cs108.multiplayer.client.MessageFormatter} - * TODO: imlement in {@link ch.unibas.dmi.dbis.cs108.multiplayer.server.JServerProtocolParser} - * TODO: add the Servers reaction, i.e. sending a list of lobbies. * Represents a clients' request for a list of lobbies */ public static final String listLobbies = "LISTL"; + /** + * Represents a clients' request for a list of all players within the lobby. + */ + public static final String listPlayersInLobby = "LISTP"; + + /** + * Client requests to join the Lobby with the given number, for example, + * {@code JOINL$2} means the client wants to join lobby 2. + * todo: document handling when lobby is already full + */ + public static final String joinLobby = "JOINL"; + + /** + * Client requests to leave whatever lobby they're in. + */ + public static final String leaveLobby = "LEAVL"; + + /** + * Whisper chat. Syntax: {@code WHISP$recipient's username$message} + */ + public static final String whisper ="WHISP"; + + /** * A Client decides to start the game. */ @@ -114,11 +143,19 @@ public class Protocol { public static final String pingFromServer = "SPING"; /** - * prints out incoming chat messages / announcements into the user's console. any string that + * prints out incoming announcements into the user's console. any string that + * follows CONSM$ is printed as is, so the message that follows already has to be formatted the + * way it should be shown to the client. + */ + public static final String printToClientConsole = "CONSM"; + + /** + * prints out incoming chat messages into the user's chat. any string that * follows CHATM$ is printed as is, so the message that follows already has to be formatted the * way it should be shown to the client. */ - public static final String printToClientConsole = "CHATM"; + public static final String printToClientChat = "CHATM"; + /** * Server confirms the client's quit request, meaning that the client can now close its @@ -138,6 +175,11 @@ public class Protocol { */ public static final String serverRequestsHumanVote = "HVOTR"; + /** + * todo: doc + */ + public static final String serverDeliversLobbyList = "LLIST"; //todo: do we need this? + diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/helpers/Protocol.txt b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/helpers/Protocol.txt index 550996a..5847f57 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/helpers/Protocol.txt +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/helpers/Protocol.txt @@ -30,6 +30,8 @@ Implemented: * SPING Ping from server to client * PINGB Pingback from client to server. * QUITC Confirms to the client that they are being disconnected from the server. + * LLIST$LobbyIDAndAdmin + Response to LISTL. Parameter is a string. Future / planned: * MSGRS "Message received": Paramaters: a string detailing to the client that and what the server received as command. diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/server/ClientHandler.java b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/server/ClientHandler.java index e6473ee..df144f0 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/server/ClientHandler.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/server/ClientHandler.java @@ -6,6 +6,7 @@ import ch.unibas.dmi.dbis.cs108.gamelogic.TrainOverflow; import ch.unibas.dmi.dbis.cs108.gamelogic.VoteHandler; import ch.unibas.dmi.dbis.cs108.multiplayer.helpers.Protocol; import ch.unibas.dmi.dbis.cs108.multiplayer.helpers.ServerPinger; + import java.io.*; import java.net.InetAddress; import java.net.Socket; @@ -16,6 +17,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class ClientHandler implements Runnable { + public static final Logger LOGGER = LogManager.getLogger(); public static final BudaLogConfig l = new BudaLogConfig(LOGGER); @@ -25,7 +27,6 @@ public class ClientHandler implements Runnable { private Socket socket; private InetAddress ip; - /** * notes if the client has formally logged in yet. If connecting through the normal Client class, * the client is logged in automatically, if connecting though some external application, the @@ -42,7 +43,8 @@ public class ClientHandler implements Runnable { /** * Implements the login logic in client-server architecture. - * @param ip the ip of the client, used for re-connection. + * + * @param ip the ip of the client, used for re-connection. * @param socket the socket on which to make the connection. */ public ClientHandler(Socket socket, InetAddress ip) { @@ -50,7 +52,7 @@ public class ClientHandler implements Runnable { this.ip = ip; this.socket = socket; this.out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); - this.in = new BufferedReader(new InputStreamReader((socket.getInputStream()))); + this.in = new BufferedReader(new InputStreamReader(socket.getInputStream())); this.loggedIn = false; this.clientUserName = nameDuplicateChecker.checkName("U.N. Owen"); connectedClients.add(this); @@ -77,16 +79,13 @@ public class ClientHandler implements Runnable { /** * Needed to fill a train with client TODO: how do lobbies fit here? + * * @return the HashSet of Connected Clients */ public static HashSet getConnectedClients() { return connectedClients; } - public static HashSet getLobby() { - return lobby; - } - public static HashSet getGhostClients() { return ghostClients; } @@ -119,42 +118,70 @@ public class ClientHandler implements Runnable { break; } } + LOGGER.debug(this.getClientUserName() + " reached end of run()"); } /** - * Lets the client change their username, if the username is already taken, a similar - * option is chosen. - * + * Lets the client change their username, if the username is already taken, a similar option is + * chosen. * @param newName The desired new name to replace the old one with. */ public void changeUsername(String newName) { String helper = this.getClientUserName(); this.clientUserName = nameDuplicateChecker.checkName(newName); - broadcastAnnouncement(helper + " has changed their nickname to " + clientUserName); + broadcastAnnouncementToAll(helper + " has changed their nickname to " + clientUserName); } /** - * Sets the client's username on login, if the username is already taken, a similar - * option is chosen. Functionally, the only difference between this method and changeUsername - * is that it doesn't print out the name change. + * Sets the client's username on login, if the username is already taken, a similar option is + * chosen. Functionally, the only difference between this method and changeUsername is that it + * doesn't print out the name change. * * @param name The desired name. */ public void setUsernameOnLogin(String name) { this.clientUserName = nameDuplicateChecker.checkName(name); - broadcastAnnouncement( clientUserName + " has joined the Server"); + broadcastAnnouncementToAll(clientUserName + " has joined the Server"); + + /* The following lines could be un-commented to provide Lobby information on login + sendAnnouncementToClient("Welcome, " + clientUserName + "!"); + sendAnnouncementToClient("Here are the currently open Lobbies:"); + listLobbies(); + */ } /** - * Broadcasts a chat Message to all active clients in the form "Username: @msg" - * + * Returns the Lobby this ClientHandler is in. If this ClientHandler is not in a Lobby, + * it returns null. + */ + public Lobby getLobby() { + try { + return Lobby.getLobbyFromID(Lobby.clientIsInLobby(this)); + } catch (Exception e) { + return null; + } + } + + /** + * Broadcasts a chat Message to all clients in the same lobby in the form "Username: @msg" + * If this client isn't in a lobby, it instead sends the message to everyone not in a lobby * @param msg the Message to be broadcast */ - public void broadcastChatMessage(String msg) { - for (ClientHandler client : connectedClients) { - client.sendMsgToClient(Protocol.printToClientConsole + "$" + clientUserName + ": " + msg); + public void broadcastChatMessageToLobby(String msg) { + Lobby l = getLobby(); + if (l != null) { + for (ClientHandler client : l.getLobbyClients()) { + client.sendMsgToClient(Protocol.printToClientChat + "$" + clientUserName + ": " + msg); + } + } else { + //send msg to all clients who are not in a lobby. + for (ClientHandler client: connectedClients) { + if (Lobby.clientIsInLobby(client) == -1) { + client.sendMsgToClient(Protocol.printToClientChat + "$" + clientUserName + ": " + msg); + } + } } } @@ -170,26 +197,71 @@ public class ClientHandler implements Runnable { } /** - * Broadcasts a non-chat Message to all active clients. This can be used for server - * messages / announcements rather than chat messages. The message will be printed to the user - * exactly as it is given to this method. Unlike broadcastChatMessage, it will also be printed - * onto the server console. - * + * Broadcasts a chat Message to all clients across all lobbies & clients who are not in a lobby + * in the form "Username: @msg" * @param msg the Message to be broadcast */ - public void broadcastAnnouncement(String msg) { + public void broadcastChatMessageToAll(String msg) { + for (ClientHandler client : connectedClients) { + client.sendMsgToClient(Protocol.printToClientChat + "$" + clientUserName + ": " + msg); + } + } + + /** + * Broadcasts a non-chat Message to all active clients. This can be used for server messages / + * announcements rather than chat messages. The message will be printed to the user exactly as it + * is given to this method. Unlike eg. broadcastChatMessageToLobby, it will also be printed onto the server + * console. + * @param msg the Message to be broadcast. Does not have to be protocol-formatted, this method will take care of that. + */ + public static void broadcastAnnouncementToAll(String msg) { System.out.println(msg); for (ClientHandler client : connectedClients) { client.sendMsgToClient(Protocol.printToClientConsole + "$" + msg); } } - /** Sends a given message to client. The message has to already be protocol-formatted. ALL + /** + * Broadcasts a non-chat Message to all clients in the same lobby. This can be used for server messages / + * announcements rather than chat messages. The message will be printed to the user exactly as it + * is given to this method. The announcement will not be printed on the server console. + * If this clienthandler is not in a lobby, it will instead broadcast to all clients. + * + * @param msg the Message to be broadcast. Does not have to be protocol-formatted, this method will take care of that. + */ + public void broadcastAnnouncementToLobby(String msg) { + Lobby l = getLobby(); + if (l != null) { + //System.out.println(msg); we can-comment this if you want lobby-announcements to print on the server console as well. + for (ClientHandler client : l.getLobbyClients()) { + client.sendMsgToClient(Protocol.printToClientConsole + "$" + msg); + } + } else { + LOGGER.debug("Could not send announcements; probably client isn't in a lobby." + + "Will broadcast across all lobbies now."); + broadcastAnnouncementToAll(msg); + } + } + + /** + * Sends a message only to the specified user, as well as sending a confirmation to the user who sent the message + * that it has been sent. Syntax: + * @param target MUST NOT BE NULL! + */ + public void whisper(String msg, ClientHandler target) { + target.sendMsgToClient(Protocol.printToClientChat + "$" + this.getClientUserName() + " whispers: " + msg); + sendMsgToClient(Protocol.printToClientChat + "$You whispered to " + target.getClientUserName() + ": " + msg); + } + + /** + * Sends a given message to this client. The message has to already be protocol-formatted. ALL * communication with the client has to happen via this method! + * * @param msg the given message. Should already be protocol-formatted. */ public void sendMsgToClient(String msg) { try { + //if(!msg.equals("SPING"))LOGGER.debug("Message sent to client: " + msg); out.write(msg); out.newLine(); out.flush(); @@ -239,15 +311,26 @@ public class ClientHandler implements Runnable { } /** - * Removes & disconnects the client. To be used if a severe connection loss is detected (i.e. if trying to - * send / receive a message throws an exception, not just if ping-pong detects a connection loss). - * This is very similar to removeClientOnLogout(), however removeClientOnLogout() should only be used for - * regular quitting, since removeClientOnLogout() cannot handle a client with connection loss. + * Sends an announcement to just this client. Essentially the same as broadcastAnnouncementToAll except + * it only sends an announcement to just this client instead of everyone. + * Can be used for private non-chat messages (e.g. "You are now a ghost"). + */ + public void sendAnnouncementToClient(String msg) { + sendMsgToClient(Protocol.printToClientConsole + "$" + msg); + } + + /** + * Removes & disconnects the client. To be used if a severe connection loss is detected (i.e. if + * trying to send / receive a message throws an exception, not just if ping-pong detects a + * connection loss). This is very similar to removeClientOnLogout(), however + * removeClientOnLogout() should only be used for regular quitting, since removeClientOnLogout() + * cannot handle a client with connection loss. */ public void removeClientOnConnectionLoss() { connectedClients.remove(this); disconnectClient(); - broadcastAnnouncement(getClientUserName() + " has left the server due to a connection loss."); + leaveLobby(); + broadcastAnnouncementToAll(getClientUserName() + " has left the server due to a connection loss."); disconnectedClients.add(this); } @@ -257,14 +340,108 @@ public class ClientHandler implements Runnable { * removeClientOnConnectionLoss() if the client has to be removed due to a connection loss. */ public void removeClientOnLogout() { - broadcastAnnouncement(getClientUserName() + " has left the server."); - sendMsgToClient(Protocol.serverConfirmQuit); //todo: protocol + broadcastAnnouncementToAll(getClientUserName() + " has left the server."); + sendMsgToClient(Protocol.serverConfirmQuit); connectedClients.remove(this); + leaveLobby(); disconnectClient(); } /** - * Closes the client's socket, in, and out. + * Invoked by Protocol.createNewLobby. Creates a new lobby with the ClientHandler as admin and + * adds the lobby to the server data. + */ + public void createNewLobby() { + if (Lobby.clientIsInLobby(this) == -1) { + Lobby newGame = new Lobby(this); + } else { + sendAnnouncementToClient("You are already in lobby nr. " + Lobby.clientIsInLobby(this)); + } + } + + /** + * The client wants to join the lobby with the index i. + * //todo: needs more doc. + * @param i + */ + public void joinLobby(int i) { + Lobby l = Lobby.getLobbyFromID(i); + if (l != null) { + l.addPlayer(this); + } else { + sendAnnouncementToClient("Invalid Lobby nr."); + sendAnnouncementToClient("use LISTL to list lobbies"); + } + + } + + /** + * If the client is in a Lobby, they leave it. Otherwise, this method does nothing. + */ + public void leaveLobby() { + Lobby l = Lobby.getLobbyFromID(Lobby.clientIsInLobby(this)); + if (l != null) { + l.removePlayer(this); + } + } + + + /** + * Lists all lobbies and their members, along with players outside lobbies + * to this clientHandler's client as an announcement. + */ + public void listLobbies() { + if (Lobby.lobbies.isEmpty()) { + sendAnnouncementToClient("No open Lobbies."); + } else { + for (Lobby l : Lobby.lobbies) { + sendAnnouncementToClient("Lobby nr. " + l.getLobbyID() + ":"); + for (ClientHandler c : l.getLobbyClients()) { + if (c.equals(l.getAdmin())) { + sendAnnouncementToClient(" -" + c.getClientUserName() + " (admin)"); + } else { + sendAnnouncementToClient(" -" + c.getClientUserName()); + } + } + } + } + boolean helper = false; //used to print "Clients not in lobbies" only once, if needed. + for (ClientHandler c: connectedClients) { + if (Lobby.clientIsInLobby(c) == -1) { + if (!helper) { + helper = true; + sendAnnouncementToClient("Clients not in lobbies:"); + } + sendAnnouncementToClient(" -" + c.getClientUserName()); + } + } + if (!helper) { + sendAnnouncementToClient("No clients outside of lobbies"); + } + } + + /** + * Lists all players in the client's lobby. If the client is not in a Lobby, it will say + * "You are not in a lobby." + */ + public void listPlayersInLobby() { + Lobby l = getLobby(); + if (l != null) { + sendAnnouncementToClient("Players in lobby nr. " + l.getLobbyID() + ":"); + for (ClientHandler c : l.getLobbyClients()) { + if (c.equals(l.getAdmin())) { + sendAnnouncementToClient(" -" + c.getClientUserName() + " (admin)"); + } else { + sendAnnouncementToClient(" -" + c.getClientUserName()); + } + } + } else { + sendAnnouncementToClient("You are not in a lobby."); + } + } + + /** + * Closes the client's socket, in, and out. and removes from global list of clients. */ public void disconnectClient() { socket = this.getSocket(); diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/server/JServerProtocolParser.java b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/server/JServerProtocolParser.java index df365a4..06a4120 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/server/JServerProtocolParser.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/server/JServerProtocolParser.java @@ -16,11 +16,6 @@ public class JServerProtocolParser { public static final Logger LOGGER = LogManager.getLogger(); public static final BudaLogConfig l = new BudaLogConfig(LOGGER); - /** - * jsdcjkhcsdjksdacjkn - */ - public static final String CHATA = "CHATA"; - /** * Used by the server (i.e. ClientHandler{@link ClientHandler}) to parse an incoming protocol @@ -43,7 +38,29 @@ public class JServerProtocolParser { } switch (header) { case Protocol.chatMsgToAll: - h.broadcastChatMessage(msg.substring(6)); + h.broadcastChatMessageToAll(msg.substring(6)); + break; + case Protocol.chatMsgToLobby: + h.broadcastChatMessageToLobby(msg.substring(6)); + break; + case Protocol.whisper: + //find ClientHandler + try { + ClientHandler target = null; + String targetName = msg.substring(6, msg.indexOf("$", 6)); + String chatMsg = msg.substring(msg.indexOf("$", 6)+1); + System.out.println(targetName); + System.out.println(chatMsg); + for (ClientHandler c : ClientHandler.getConnectedClients()) { + if (c.getClientUserName().equals(targetName)) { + target = c; + } + } + assert target != null; + h.whisper(chatMsg, target); + } catch (Exception ignored) { + h.sendAnnouncementToClient("Something went wrong."); + } break; case Protocol.clientLogin: h.setLoggedIn(true); @@ -65,15 +82,26 @@ public class JServerProtocolParser { case Protocol.clientQuitRequest: h.removeClientOnLogout(); break; - case Protocol.createNewGame: - // TODO add h.openLobby(h) method - LOGGER.debug(Protocol.createNewGame - + " command reached in JServerProtocolParser. Command issued by: " - + h.getClientUserName()); + case Protocol.joinLobby: + try { + int i = Integer.parseInt(msg.substring(6, 7)); + h.joinLobby(i); + } catch (Exception e) { + h.sendMsgToClient(Protocol.printToClientConsole + + "$Invalid input. Please use JOINL$1 to join Lobby 1, for example."); + } + break; + case Protocol.createNewLobby: + h.createNewLobby(); break; case Protocol.listLobbies: - //TODO: add action - LOGGER.debug(Protocol.listLobbies + " command received from: " + h.getClientUserName()); + h.listLobbies(); + break; + case Protocol.listPlayersInLobby: + h.listPlayersInLobby(); + break; + case Protocol.leaveLobby: + h.leaveLobby(); break; case Protocol.votedFor: LOGGER.debug("Made it here"); diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/server/Lobby.java b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/server/Lobby.java new file mode 100644 index 0000000..68b46ee --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/server/Lobby.java @@ -0,0 +1,170 @@ +package ch.unibas.dmi.dbis.cs108.multiplayer.server; + +import ch.unibas.dmi.dbis.cs108.BudaLogConfig; + +import java.util.HashSet; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * Use: If a client sends a CRTLB command the server should create a lobby with the client as admin. + * In this state, up to 5 other clients (so 6 in total) are able to join this lobby. Once the admin + * feels like it, they can start a game. + */ +public class Lobby { + + public static final Logger LOGGER = LogManager.getLogger(); + public static final BudaLogConfig l = new BudaLogConfig(LOGGER); + public static HashSet lobbies = new HashSet<>(); + + private static final int MAX_NO_OF_CLIENTS = 6; + + + /** + * The Person who created the game and can configure it and decide to start once enough lobbyClients + * have entered the lobby. + */ + private final ClientHandler admin; + + /** + * Everyone who's in the lobby. + */ + private HashSet lobbyClients = new HashSet<>(6); + + private final int lobbyID; + + + /** + * Constructor. Sets the admin to who created the lobby. Adds the admin to the list of lobbyClients. + * Increases the number of lobbyClients from 0 to 1. + * + * @param admin the Client who called CRTGM + */ + public Lobby(ClientHandler admin) { + this.admin = admin; + this.lobbyClients.add(admin); + lobbies.add(this); + int helper = 1; + while (getLobbyFromID(helper) != null) { + helper++; + } + this.lobbyID = helper; + ClientHandler.broadcastAnnouncementToAll("New Lobby created by " + admin.getClientUserName() + + ". This lobby's ID: " + this.lobbyID); + } + + /** + * Getter + * + * @return the admin of the lobby. + */ + public ClientHandler getAdmin() { + return this.admin; + } + + /** + * getter for the lobby ID + * @return lobbyID as set in constructor. + */ + public int getLobbyID() { + return this.lobbyID; + } + + /** + * Returns the list containing lobbyClients currently in the lobby + * + * @return list of lobbyClients + */ + public HashSet getLobbyClients() { + return this.lobbyClients; + } + + /** + * Returns the lobby with the desired LobbyID. + * For example, getLobbyFromID(5) returns the lobby whose LobbyID is 5. + * If no such lobby exists, it returns null. + */ + public static Lobby getLobbyFromID(int i) { + for (Lobby l: lobbies) { + if (l.getLobbyID() == i) { + return l; + } + } + return null; + } + + /** + * Returns the ID of the lobby that the client is in. If the client is not in any + * lobby, it returns -1. + */ + public static int clientIsInLobby(ClientHandler h) { + for (Lobby l: lobbies) { + for (ClientHandler clientHandler: l.getLobbyClients()) { + if (h.equals(clientHandler)) { + return l.getLobbyID(); + } + } + } + return -1; + } + + /** + * Adds a player to the lobby. Returns true if successful. + * TODO: add an appropriate response. Currently hardcoded. + * @param client who wants to join the lobby. + */ + public synchronized boolean addPlayer(ClientHandler client) { + if (lobbyClients.size() < MAX_NO_OF_CLIENTS) { + if (clientIsInLobby(client) == -1) { + lobbyClients.add(client); + ClientHandler.broadcastAnnouncementToAll(client.getClientUserName() + " has joined lobby " + this.getLobbyID()); + //LOGGER.debug(client.getClientUserName() + " has been added to Lobby with ID: " + lobbyID + // + ". Current number of lobbyClients in this lobby: " + lobbyClients.size()); + return true; + } else { + client.sendAnnouncementToClient("You are already in lobby nr. " + clientIsInLobby(client)); + } + } else { + client.sendAnnouncementToClient("This lobby is full. Please try joining a different lobby or create a new lobby"); + } + return false; + } + + /** + * Does what is says on the box. Needs to be called when a client disconnects for some reason and + * cannot reconnect. I.E. when the server closes the connection to that client, the client should + * be removed from the list. + * + * @param player that is to be removed + * @return true if a player was found and removed. Used for debugging. + */ + public synchronized boolean removePlayer(ClientHandler player) { + //if the player who leaves the lobby is the admin, the lobby is closed. + if (player.equals(getAdmin())) { + ClientHandler.broadcastAnnouncementToAll(player.getClientUserName() + " has closed lobby nr. " + this.getLobbyID()); + closeLobby(); + } else if (this.getLobbyClients().remove(player)){ + ClientHandler.broadcastAnnouncementToAll(player.getClientUserName() + " has left lobby nr. " + this.getLobbyID()); + return true; + } + return false; + } + + /** + * Closes the lobby. + * + */ + public void closeLobby() { + lobbies.remove(this); + ClientHandler.broadcastAnnouncementToAll("Lobby nr. " + this.getLobbyID() + " has been closed."); + /* + Todo: theoretically, this is enough to close a lobby. + ClientHandlers dont have to manually be removed from the lobby + since if the lobby is removed from the lobbies + hash set then e.g. clientIsInLobby will not be able to tell that these clients + are theoretically still in this lobby. However, in the future we might implement + removing the clients anyways. + */ + } + +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/server/Server.java b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/server/Server.java index 012a4b7..d67f759 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/server/Server.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/server/Server.java @@ -1,6 +1,7 @@ package ch.unibas.dmi.dbis.cs108.multiplayer.server; import ch.unibas.dmi.dbis.cs108.BudaLogConfig; + import java.io.*; import java.net.ServerSocket; import java.net.Socket; @@ -32,7 +33,7 @@ public class Server { Socket socket = serverSocket.accept(); ClientHandler nextClient = new ClientHandler(socket, socket.getInetAddress()); Thread th = new Thread(nextClient); - connectedClients.add(nextClient); + connectedClients.add(nextClient); // will leave be for now th.start(); } } catch (IOException e) { diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/sebaschi/CentralServerData.java b/src/main/java/ch/unibas/dmi/dbis/cs108/sebaschi/CentralServerData.java deleted file mode 100644 index ed92d4c..0000000 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/sebaschi/CentralServerData.java +++ /dev/null @@ -1,30 +0,0 @@ -package ch.unibas.dmi.dbis.cs108.sebaschi; - -import ch.unibas.dmi.dbis.cs108.BudaLogConfig; -import ch.unibas.dmi.dbis.cs108.gamelogic.Game; -import ch.unibas.dmi.dbis.cs108.multiplayer.client.Client; -import ch.unibas.dmi.dbis.cs108.multiplayer.server.ClientHandler; -import java.net.Socket; -import java.util.Map; -import java.util.Set; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -/** - * This Class Represents an Object containing different Maps, Lists and Sets wherein a server object - * can find all needed data. An instance of this object can also be passed to other class-objects if - * they need the same data. This Class is used to query for information in collections. - */ -public class CentralServerData { - - public static final Logger LOGGER = LogManager.getLogger(); - public static final BudaLogConfig l = new BudaLogConfig(LOGGER); - - private Set clientsOnServer; - private Set activeGames; - private Set gamesOpenToJoin; - - private Map clientSocketMap; - private Map socketClientMap; - private Map gameClientMap; -} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/sebaschi/GameSessionData.java b/src/main/java/ch/unibas/dmi/dbis/cs108/sebaschi/GameSessionData.java deleted file mode 100644 index fe2b850..0000000 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/sebaschi/GameSessionData.java +++ /dev/null @@ -1,25 +0,0 @@ -package ch.unibas.dmi.dbis.cs108.sebaschi; - -import ch.unibas.dmi.dbis.cs108.BudaLogConfig; -import ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur.Ghost; -import ch.unibas.dmi.dbis.cs108.gamelogic.klassenstruktur.Passenger; -import java.io.BufferedOutputStream; -import java.util.Set; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -/** - * Class that shall contain all non-game logik information relevant to a game session needed for - * client-server and client-client communication. - */ -public class GameSessionData { - - public static final Logger LOGGER = LogManager.getLogger(); - public static final BudaLogConfig l = new BudaLogConfig(LOGGER); - - CentralServerData globalData; - - Set passengers; - Set clientOutputStreams; - Set ghosts; -} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/sebaschi/Lobby.java b/src/main/java/ch/unibas/dmi/dbis/cs108/sebaschi/Lobby.java deleted file mode 100644 index 5109794..0000000 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/sebaschi/Lobby.java +++ /dev/null @@ -1,96 +0,0 @@ -package ch.unibas.dmi.dbis.cs108.sebaschi; - -import ch.unibas.dmi.dbis.cs108.BudaLogConfig; -import ch.unibas.dmi.dbis.cs108.multiplayer.helpers.Protocol; -import ch.unibas.dmi.dbis.cs108.multiplayer.server.ClientHandler; -import java.util.ArrayList; -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -/** - * Use: If a client sends a CRTGM command the server should create a lobby with the client as admin. - * In this state, up to 5 other clients (so 6 in total) are able to join this lobby. Once the admin - * feels like it, he can start a game. - * TODO: is all data in here or should GameSessionData be used to collect all relevant data? - */ -public class Lobby { - - public static final Logger LOGGER = LogManager.getLogger(); - public static final BudaLogConfig l = new BudaLogConfig(LOGGER); - - private static final int MAX_NO_OF_CLIENTS = 6; - public static int lobbies; - - //TODO - CentralServerData serverData; - - /** - * The Person who created the game and can configure it and decide to start once enough players - * have entered the lobby. - */ - private final ClientHandler admin; - - /** - * Everyone who's in the lobby. - */ - private List players = new ArrayList<>(6); - - - private int numberOfPlayersInLobby; - //TODO maybe it makes sense to have LobbyID Class? - private final int lobbyID = lobbies++; - - - static { - lobbies = 0; - } - - - /** - * Constructor. Sets the admin to who created the lobby. Adds the admin to the list of players. - * Increases the number of players from 0 to 1. - * - * @param admin the Client who called CRTGM - */ - public Lobby(ClientHandler admin) { - this.admin = admin; - this.players.add(admin); - this.numberOfPlayersInLobby = 1; - lobbies++; - LOGGER.debug("New Lobby created by " + admin.getClientUserName() + ". This lobby's ID: " - + this.lobbyID); - } - - /** - * Getter - * - * @return the admin of the lobby. - */ - public ClientHandler getAdmin() { - return this.admin; - } - - /** - * Adds a player to the lobby. - * TODO: ad an appropriate response. Currently hardcoded. - * TODO: Does this method need to implemented somewhere else, e.g. in the ClientHandler? - * @param player who wants to join the lobby. - */ - public void addPlayer(ClientHandler player) { - if (players.size() <= MAX_NO_OF_CLIENTS) { - players.add(player); - numberOfPlayersInLobby++; - LOGGER.debug(player.getClientUserName() + " has been added to Lobby with ID: " + lobbyID - + ". Current number of players in this lobby: " + players.size()); - } else { - LOGGER.debug( - player.getClientUserName() + " could not be added to lobby. No. of players in lobby: " - + numberOfPlayersInLobby); - //TODO: does this have to be formatted in any way to conform to protocol? - player.sendMsgToClient(Protocol.printToClientConsole + - "$The lobby is full. Please try joining a different lobby or create a new game"); - } - } - -} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/sebaschi/LoginClient.java b/src/main/java/ch/unibas/dmi/dbis/cs108/sebaschi/LoginClient.java deleted file mode 100644 index 906a657..0000000 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/sebaschi/LoginClient.java +++ /dev/null @@ -1,8 +0,0 @@ -package ch.unibas.dmi.dbis.cs108.sebaschi; - -/** - * Implement the login sequenz when a client wnats to connect to the server. - */ -public class LoginClient { -//TODO implement if needed -} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/sebaschi/ServerLobby.java b/src/main/java/ch/unibas/dmi/dbis/cs108/sebaschi/ServerLobby.java deleted file mode 100644 index b4c48f7..0000000 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/sebaschi/ServerLobby.java +++ /dev/null @@ -1,25 +0,0 @@ -package ch.unibas.dmi.dbis.cs108.sebaschi; - -import ch.unibas.dmi.dbis.cs108.multiplayer.server.ClientHandler; -import java.util.ArrayList; -import java.util.List; - -/** - * This class is just everyone on the server, which games are open to join, who is in session. I.E. - * the context of the game just after joining the server, before starting a game and entering into a - * lobby. - */ -public class ServerLobby { - - private static List openLobbies; - private static List allClients; - - static { - openLobbies = new ArrayList<>(); - allClients = new ArrayList<>(); - } - - public static List getOpenLobbies() { - return openLobbies; - } -} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/sebaschi/Session.java b/src/main/java/ch/unibas/dmi/dbis/cs108/sebaschi/Session.java deleted file mode 100644 index 6b4d0c8..0000000 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/sebaschi/Session.java +++ /dev/null @@ -1,9 +0,0 @@ -package ch.unibas.dmi.dbis.cs108.sebaschi; - -import java.util.ArrayList; -import java.util.List; - -public class Session { - List Lobbies = new ArrayList<>(); - -}