refactor: major refactor for UserManager, and comments for LobbyManager
This commit is contained in:
parent
3ea1cc6cf5
commit
e40218b2cf
@ -34,15 +34,19 @@ public class LobbyManager {
|
|||||||
private final HashMap<SUID, Participant> participants = 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) {
|
public boolean handleConnect(Client client, AtomicBoolean running) {
|
||||||
if(participants.containsKey(client.id)) {
|
if (participants.containsKey(client.id)) {
|
||||||
running.set(true);
|
running.set(true);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean handleReady(Client client, PlayerReadyMessage message) {
|
public boolean handleReady(Client client, PlayerReadyMessage message) {
|
||||||
if(participants.containsKey(client.id)) {
|
if (participants.containsKey(client.id)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,7 +55,7 @@ public class LobbyManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean handleReconnect(Client client) {
|
public boolean handleReconnect(Client client) {
|
||||||
if(!participants.containsKey(client.id)) {
|
if (!participants.containsKey(client.id)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,7 +64,7 @@ public class LobbyManager {
|
|||||||
|
|
||||||
LobbyConnection lobby = lobbies.get(participant.lobby);
|
LobbyConnection lobby = lobbies.get(participant.lobby);
|
||||||
|
|
||||||
if(lobby == null) {
|
if (lobby == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,20 +73,28 @@ public class LobbyManager {
|
|||||||
return true;
|
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) {
|
public boolean handleSelection(Client client, CharacterSelectionMessage message) {
|
||||||
if(!participants.containsKey(client.id)) {
|
if (!participants.containsKey(client.id)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Participant participant = participants.get(client.id);
|
Participant participant = participants.get(client.id);
|
||||||
|
|
||||||
if(participant.state != ParticipantState.Assigned) {
|
if (participant.state != ParticipantState.Assigned) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
LobbyConnection lobby = lobbies.get(participant.lobby);
|
LobbyConnection lobby = lobbies.get(participant.lobby);
|
||||||
|
|
||||||
if(lobby == null) {
|
if (lobby == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,13 +103,13 @@ public class LobbyManager {
|
|||||||
CharacterProperties[] options = lobby.options.get(participant.type);
|
CharacterProperties[] options = lobby.options.get(participant.type);
|
||||||
|
|
||||||
int n = 0;
|
int n = 0;
|
||||||
for(int i = 0; i < 12; i++) {
|
for (int i = 0; i < 12; i++) {
|
||||||
if(message.characters[i]) {
|
if (message.characters[i]) {
|
||||||
selected[n++] = options[i].characterID;
|
selected[n++] = options[i].characterID;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(n != 6) {
|
if (n != 6) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,27 +121,34 @@ public class LobbyManager {
|
|||||||
response.selectionComplete = complete;
|
response.selectionComplete = complete;
|
||||||
participant.sendMessage(response);
|
participant.sendMessage(response);
|
||||||
|
|
||||||
if(complete) {
|
if (complete) {
|
||||||
LobbyRunner.getInstance().startLobby(lobby);
|
LobbyRunner.getInstance().startLobby(lobby);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
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) {
|
public boolean handleRequests(Client client, RequestMessage message) {
|
||||||
if(!participants.containsKey(client.id)) {
|
if (!participants.containsKey(client.id)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Participant participant = participants.get(client.id);
|
Participant participant = participants.get(client.id);
|
||||||
|
|
||||||
if(participant.state != ParticipantState.Playing) {
|
if (participant.state != ParticipantState.Playing) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
LobbyConnection lobby = lobbies.get(participant.lobby);
|
LobbyConnection lobby = lobbies.get(participant.lobby);
|
||||||
|
|
||||||
if(lobby == null) {
|
if (lobby == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,11 +158,11 @@ public class LobbyManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void handleDisconnect(Client client, boolean byRemote) {
|
public void handleDisconnect(Client client, boolean byRemote) {
|
||||||
if(!participants.containsKey(client.id)) {
|
if (!participants.containsKey(client.id)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!byRemote) {
|
if (!byRemote) {
|
||||||
participants.remove(client.id);
|
participants.remove(client.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,7 +170,7 @@ public class LobbyManager {
|
|||||||
|
|
||||||
LobbyConnection lobby = lobbies.get(participant.lobby);
|
LobbyConnection lobby = lobbies.get(participant.lobby);
|
||||||
|
|
||||||
if(lobby == null) {
|
if (lobby == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,8 +183,8 @@ public class LobbyManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void addParticipant(Client client, String lobbyID, RoleEnum role) {
|
private void addParticipant(Client client, String lobbyID, RoleEnum role) {
|
||||||
if(!lobbies.containsKey(lobbyID)) {
|
if (!lobbies.containsKey(lobbyID)) {
|
||||||
if(!LobbyRunner.getInstance().canAddLobby()) {
|
if (!LobbyRunner.getInstance().canAddLobby()) {
|
||||||
UserManager.getInstance().removeClient(client, "The server is currently full.");
|
UserManager.getInstance().removeClient(client, "The server is currently full.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -176,7 +195,7 @@ public class LobbyManager {
|
|||||||
|
|
||||||
ParticipantType type = lobby.freeSlot();
|
ParticipantType type = lobby.freeSlot();
|
||||||
|
|
||||||
if(type == ParticipantType.Spectator && role != RoleEnum.SPECTATOR) {
|
if (type == ParticipantType.Spectator && role != RoleEnum.SPECTATOR) {
|
||||||
UserManager.getInstance().removeClient(client, "The game is already full. Please connect as a spectator instead.");
|
UserManager.getInstance().removeClient(client, "The game is already full. Please connect as a spectator instead.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -186,12 +205,12 @@ public class LobbyManager {
|
|||||||
|
|
||||||
lobby.addParticipant(participant);
|
lobby.addParticipant(participant);
|
||||||
|
|
||||||
if(type != ParticipantType.Spectator) {
|
if (type != ParticipantType.Spectator) {
|
||||||
GameAssignmentMessage response = new GameAssignmentMessage();
|
GameAssignmentMessage response = new GameAssignmentMessage();
|
||||||
response.gameID = lobby.gameID;
|
response.gameID = lobby.gameID;
|
||||||
response.characterSelection = lobby.options.get(type);
|
response.characterSelection = lobby.options.get(type);
|
||||||
participant.sendMessage(response);
|
participant.sendMessage(response);
|
||||||
}else {
|
} else {
|
||||||
GeneralAssignmentMessage response = new GeneralAssignmentMessage();
|
GeneralAssignmentMessage response = new GeneralAssignmentMessage();
|
||||||
response.gameID = lobby.gameID;
|
response.gameID = lobby.gameID;
|
||||||
participant.sendMessage(response);
|
participant.sendMessage(response);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package uulm.teamname.marvelous.server.netconnector;
|
package uulm.teamname.marvelous.server.netconnector;
|
||||||
|
|
||||||
import org.java_websocket.framing.CloseFrame;
|
import org.java_websocket.framing.CloseFrame;
|
||||||
|
import org.tinylog.Logger;
|
||||||
import uulm.teamname.marvelous.gamelibrary.json.JSON;
|
import uulm.teamname.marvelous.gamelibrary.json.JSON;
|
||||||
import uulm.teamname.marvelous.gamelibrary.json.ValidationUtility;
|
import uulm.teamname.marvelous.gamelibrary.json.ValidationUtility;
|
||||||
import uulm.teamname.marvelous.gamelibrary.messages.BasicMessage;
|
import uulm.teamname.marvelous.gamelibrary.messages.BasicMessage;
|
||||||
@ -45,6 +46,7 @@ public class UserManager {
|
|||||||
|
|
||||||
/** Called on a new WebSocket connection. Places the WebSocket and its ResourceDescriptor in a HashMap. */
|
/** Called on a new WebSocket connection. Places the WebSocket and its ResourceDescriptor in a HashMap. */
|
||||||
void connectUser(WebSocket conn) {
|
void connectUser(WebSocket conn) {
|
||||||
|
Logger.debug("Connected new user");
|
||||||
synchronized(clients) {
|
synchronized(clients) {
|
||||||
clients.put(conn, new Client(conn));
|
clients.put(conn, new Client(conn));
|
||||||
}
|
}
|
||||||
@ -56,15 +58,19 @@ public class UserManager {
|
|||||||
* @param message is the {@link String} sent by the connection
|
* @param message is the {@link String} sent by the connection
|
||||||
*/
|
*/
|
||||||
void messageReceived(WebSocket conn, String message) {
|
void messageReceived(WebSocket conn, String message) {
|
||||||
|
Logger.debug("Message received from {}", conn);
|
||||||
Client client = clients.get(conn);
|
Client client = clients.get(conn);
|
||||||
|
|
||||||
if(client == null) {
|
if(client == null) {
|
||||||
|
Logger.warn("messageReceived called with null-valued client. This is probably a bug.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.trace("Parsing message...");
|
||||||
Optional<BasicMessage> parsed = json.parse(message);
|
Optional<BasicMessage> parsed = json.parse(message);
|
||||||
|
|
||||||
if(parsed.isEmpty()) {
|
if(parsed.isEmpty()) {
|
||||||
|
Logger.debug("Message couldn't be parsed, sending error...");
|
||||||
client.sendError("Message could not be parsed.");
|
client.sendError("Message could not be parsed.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -73,39 +79,77 @@ public class UserManager {
|
|||||||
Optional<String> violations = ValidationUtility.validate(data);
|
Optional<String> violations = ValidationUtility.validate(data);
|
||||||
|
|
||||||
if(violations.isPresent()) {
|
if(violations.isPresent()) {
|
||||||
|
Logger.debug("The message that client sent had structural violations: '{}'. Sending error...",
|
||||||
|
violations.get());
|
||||||
client.sendError(violations.get());
|
client.sendError(violations.get());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.trace("Handling message...");
|
||||||
handleMessage(client, data);
|
handleMessage(client, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Called on closed connection. */
|
/** Called on closed connection. */
|
||||||
void disconnectUser(WebSocket conn, boolean closedByRemote) {
|
void disconnectUser(WebSocket conn, boolean closedByRemote) {
|
||||||
|
Logger.info("Disconnecting client '{}'", conn);
|
||||||
Client client = clients.get(conn);
|
Client client = clients.get(conn);
|
||||||
|
|
||||||
if(client == null) {
|
if(client == null) {
|
||||||
|
Logger.warn("disconnect called with null-valued client. This is probably a bug.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.trace("Calling handleDisconnect on LobbyManager");
|
||||||
LobbyManager.getInstance().handleDisconnect(client, closedByRemote);
|
LobbyManager.getInstance().handleDisconnect(client, closedByRemote);
|
||||||
|
|
||||||
synchronized(clients) {
|
synchronized(clients) {
|
||||||
|
Logger.debug("Removing connections from clients HashMap");
|
||||||
clients.remove(conn);
|
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) {
|
private void handleMessage(Client client, BasicMessage data) {
|
||||||
|
Logger.debug("Received message from client '{}'", client);
|
||||||
if(data instanceof HelloServerMessage) {
|
if(data instanceof HelloServerMessage) {
|
||||||
|
Logger.trace("Message was instanceof HelloServerMessage");
|
||||||
HelloServerMessage message = (HelloServerMessage) data;
|
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.state != ClientState.Blank) {
|
if(client.state != ClientState.Blank) {
|
||||||
client.sendError("Invalid message.");
|
Logger.debug("Disconnecting client as ClientState isn't Blank but {}", client.state);
|
||||||
|
client.sendError("Invalid message, as handshake already completed.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
client.id = new SUID(message.name, message.deviceID);
|
client.id = new SUID(message.name, message.deviceID);
|
||||||
|
|
||||||
|
Logger.trace("forwarding message to the LobbyManager");
|
||||||
AtomicBoolean running = new AtomicBoolean(false);
|
AtomicBoolean running = new AtomicBoolean(false);
|
||||||
if(LobbyManager.getInstance().handleConnect(client, running)) {
|
if(LobbyManager.getInstance().handleConnect(client, running)) {
|
||||||
client.state = ClientState.Ready;
|
client.state = ClientState.Ready;
|
||||||
@ -114,67 +158,100 @@ public class UserManager {
|
|||||||
response.runningGame = running.get();
|
response.runningGame = running.get();
|
||||||
client.sendMessage(response);
|
client.sendMessage(response);
|
||||||
} else {
|
} else {
|
||||||
client.sendError("Invalid message.");
|
client.sendError("Message could not be processed.");
|
||||||
}
|
}
|
||||||
} else if(data instanceof ReconnectMessage) {
|
}
|
||||||
ReconnectMessage message = (ReconnectMessage) data;
|
|
||||||
|
/** Handles a reconnectMessage, and reconnects the client if needed */
|
||||||
|
private void handleReconnectMessage(Client client, ReconnectMessage message) {
|
||||||
if(client.state != ClientState.Ready) {
|
if(client.state != ClientState.Ready) {
|
||||||
client.sendError("Invalid message.");
|
client.sendError("Invalid message.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(message.reconnect) {
|
if(message.reconnect) {
|
||||||
|
Logger.trace("Reconnecting to lobby. Forwarding reconnect instruction to the LobbyManager");
|
||||||
if(LobbyManager.getInstance().handleReconnect(client)) {
|
if(LobbyManager.getInstance().handleReconnect(client)) {
|
||||||
|
Logger.trace("Successfully reconnected client, changing state to Playing...");
|
||||||
client.state = ClientState.Playing;
|
client.state = ClientState.Playing;
|
||||||
} else {
|
} else {
|
||||||
client.sendError("Invalid message.");
|
Logger.debug("The client couldn't be reconnected properly");
|
||||||
|
client.sendError("You could not be reconnected to the Lobby");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
Logger.trace("No reconnect requested, changing ClientState to blank");
|
||||||
client.state = ClientState.Blank;
|
client.state = ClientState.Blank;
|
||||||
}
|
}
|
||||||
} else if(data instanceof PlayerReadyMessage) {
|
}
|
||||||
PlayerReadyMessage message = (PlayerReadyMessage) data;
|
|
||||||
|
/** 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.state != ClientState.Ready) {
|
if(client.state != ClientState.Ready) {
|
||||||
client.sendError("Invalid message.");
|
Logger.debug("Client wasn't in Ready state but instead in {} state, sending error...", client.state);
|
||||||
|
client.sendError("Invalid message, as client is not in Ready state");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.trace("Relaying message to LobbyManager");
|
||||||
if(message.startGame) {
|
if(message.startGame) {
|
||||||
if(LobbyManager.getInstance().handleReady(client, message)) {
|
if(LobbyManager.getInstance().handleReady(client, message)) {
|
||||||
client.state = ClientState.Assigned;
|
client.state = ClientState.Assigned;
|
||||||
} else {
|
} else {
|
||||||
|
Logger.trace("Sending error to client as message couldn't be processed properly");
|
||||||
client.sendError("Invalid message.");
|
client.sendError("Invalid message.");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
Logger.debug("Disconnecting client as game couldn't be started");
|
||||||
removeClient(client, "You got disconnected.");
|
removeClient(client, "You got disconnected.");
|
||||||
}
|
}
|
||||||
} else if(data instanceof CharacterSelectionMessage) {
|
}
|
||||||
CharacterSelectionMessage message = (CharacterSelectionMessage) data;
|
|
||||||
|
/** Handles a characterSelectionMessage, and forwards it to the LobbyManager if valid */
|
||||||
|
private void handleCharacterSelectionMessage(Client client, CharacterSelectionMessage message) {
|
||||||
|
Logger.trace("Handling CharacterSelectionMessage");
|
||||||
if(client.state != ClientState.Assigned) {
|
if(client.state != ClientState.Assigned) {
|
||||||
client.sendError("Invalid message.");
|
Logger.debug("Couldn't handle CharacterSelectionMessage as client wasn't in assignedState but in {}",
|
||||||
|
client.state);
|
||||||
|
client.sendError("Cannot select character, as client is not in the Character Selection phase");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.trace("relaying message to be handled by the LobbyManager...");
|
||||||
if(LobbyManager.getInstance().handleSelection(client, message)) {
|
if(LobbyManager.getInstance().handleSelection(client, message)) {
|
||||||
|
Logger.trace("Handled successfully");
|
||||||
client.state = ClientState.Playing;
|
client.state = ClientState.Playing;
|
||||||
} else {
|
} else {
|
||||||
client.sendError("Invalid message.");
|
client.sendError("Invalid message.");
|
||||||
}
|
}
|
||||||
} else if(data instanceof RequestMessage) {
|
}
|
||||||
RequestMessage message = (RequestMessage) data;
|
|
||||||
|
/** Handles a RequestMessage, and forwards it to the LobbyManager if valid */
|
||||||
|
private void handleRequestsMessage(Client client, RequestMessage message) {
|
||||||
|
Logger.trace("Handling RequestMessage");
|
||||||
if(client.state != ClientState.Playing) {
|
if(client.state != ClientState.Playing) {
|
||||||
client.sendError("Invalid message.");
|
Logger.debug("Couldn't handle RequestMessage as client wasn't in assignedState but in {}",
|
||||||
|
client.state);
|
||||||
|
client.sendError("Invalid message, as client is not ingame");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.trace("relaying message to be handled by the LobbyManager...");
|
||||||
if(LobbyManager.getInstance().handleRequests(client, message)) {
|
if(LobbyManager.getInstance().handleRequests(client, message)) {
|
||||||
//"👍 i approve" - the server
|
Logger.trace("Handled successfully");
|
||||||
|
//"i approve" - the server
|
||||||
} else {
|
} else {
|
||||||
|
Logger.debug("Message couldn't be handled, sending error to client");
|
||||||
client.sendError("Invalid message.");
|
client.sendError("Invalid message.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
public void removeClient(Client client, String message) {
|
||||||
GoodbyeClientMessage response = new GoodbyeClientMessage();
|
GoodbyeClientMessage response = new GoodbyeClientMessage();
|
||||||
response.message = message;
|
response.message = message;
|
||||||
|
Loading…
Reference in New Issue
Block a user