Server in working state, can be merged with working branch.

This commit is contained in:
lieght
2025-09-28 20:31:26 +02:00
committed by Bas Antonius de Jong
parent a9e63b3fcc
commit c76b7a800e
7 changed files with 399 additions and 202 deletions

View File

@@ -11,6 +11,8 @@ import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.framework.eventbus.EventFlow;
import org.toop.framework.networking.events.NetworkEvents;
import java.util.function.Supplier;
@@ -18,6 +20,8 @@ public class NetworkingClient {
private static final Logger logger = LogManager.getLogger(NetworkingClient.class);
private long connectionId;
private String host;
private int port;
private Channel channel;
private NetworkingGameClientHandler handler;
@@ -47,13 +51,23 @@ public class NetworkingClient {
});
ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
this.channel = channelFuture.channel();
this.host = host;
this.port = port;
} catch (Exception e) {
logger.error("Failed to create networking client instance", e);
}
}
public NetworkingGameClientHandler getHandler() {
return handler;
return this.handler;
}
public String getHost() {
return this.host;
}
public int getPort() {
return this.port;
}
public void setConnectionId(long connectionId) {
@@ -83,55 +97,14 @@ public class NetworkingClient {
}
}
public void login(String username) {
this.writeAndFlush("login " + username + "\n");
}
public void logout() {
this.writeAndFlush("logout\n");
}
public void sendMove(int move) {
this.writeAndFlush("move " + move + "\n"); // append \n so server receives a full line
}
public void getGamelist() {
this.writeAndFlush("get gamelist\n");
}
public void getPlayerlist() {
this.writeAndFlush("get playerlist\n");
}
public void subscribe(String gameType) {
this.writeAndFlush("subscribe " + gameType + "\n");
}
public void forfeit() {
this.writeAndFlush("forfeit\n");
}
public void challenge(String playerName, String gameType) {
this.writeAndFlush("challenge " + playerName + " " + gameType + "\n");
}
public void acceptChallenge(String challengeNumber) {
this.writeAndFlush("challenge accept " + challengeNumber + "\n");
}
public void sendChatMessage(String message) {
this.writeAndFlush("message " + "\"" + message + "\"" + "\n");
}
public void help(String command) {
this.writeAndFlush("help " + command + "\n");
}
public void closeConnection() {
if (this.channel != null && this.channel.isActive()) {
this.channel.close().addListener(future -> {
if (future.isSuccess()) {
logger.info("Connection {} closed successfully", this.channel.remoteAddress());
new EventFlow()
.addPostEvent(new NetworkEvents.ClosedConnection(this.connectionId))
.asyncPostEvent();
} else {
logger.error("Error closing connection {}. Error: {}",
this.channel.remoteAddress(),

View File

@@ -2,7 +2,6 @@ package org.toop.framework.networking;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -23,9 +22,22 @@ public class NetworkingClientManager {
new EventFlow()
.listen(this::handleStartClient)
.listen(this::handleCommand)
.listen(this::handleSendLogin)
.listen(this::handleSendLogout)
.listen(this::handleSendGetPlayerlist)
.listen(this::handleSendGetGamelist)
.listen(this::handleSendSubscribe)
.listen(this::handleSendMove)
.listen(this::handleSendChallenge)
.listen(this::handleSendAcceptChallenge)
.listen(this::handleSendForfeit)
.listen(this::handleSendMessage)
.listen(this::handleSendHelp)
.listen(this::handleSendHelpForCommand)
.listen(this::handleCloseClient)
.listen(this::getAllConnections)
.listen(this::shutdownAll);
.listen(this::handleChangeClientHost)
.listen(this::handleGetAllConnections)
.listen(this::handleShutdownAll);
logger.info("NetworkingClientManager initialized");
} catch (Exception e) {
logger.error("Failed to initialize the client manager", e);
@@ -35,7 +47,7 @@ public class NetworkingClientManager {
private long startClientRequest(String ip, int port) {
long connectionId = new SnowflakeGenerator().nextId(); // TODO: Maybe use the one generated
try { // With EventFlow
try { // With EventFlow
NetworkingClient client = new NetworkingClient(
() -> new NetworkingGameClientHandler(connectionId),
ip,
@@ -43,13 +55,30 @@ public class NetworkingClientManager {
connectionId);
client.setConnectionId(connectionId);
this.networkClients.put(connectionId, client);
logger.info("New client started successfully for {}:{}", ip, port);
} catch (Exception e) {
logger.error(e);
}
logger.info("Client {} started", connectionId);
return connectionId;
}
private long startClientRequest(String ip, int port, long clientId) {
try { // With EventFlow
NetworkingClient client = new NetworkingClient(
() -> new NetworkingGameClientHandler(clientId),
ip,
port,
clientId);
client.setConnectionId(clientId);
this.networkClients.replace(clientId, client);
logger.info("New client started successfully for {}:{}, replaced: {}", ip, port, clientId);
} catch (Exception e) {
logger.error(e);
}
logger.info("Client {} started", clientId);
return clientId;
}
private void handleStartClient(NetworkEvents.StartClient event) {
long id = this.startClientRequest(event.ip(), event.port());
new Thread(() -> {
@@ -67,54 +96,96 @@ public class NetworkingClientManager {
private void handleCommand(
NetworkEvents.SendCommand
event) { // TODO: Move this to ServerConnection class, keep it internal.
NetworkingClient client = this.networkClients.get(event.connectionId());
logger.info("Preparing to send command: {} to server: {}", event.args(), client.getId());
NetworkingClient client = this.networkClients.get(event.clientId());
String args = String.join(" ", event.args());
client.writeAndFlushnl(args);
sendCommand(client, args);
}
private void handleSendLogin(NetworkEvents.SendLogin event) {
NetworkingClient client = this.networkClients.get(event.clientId());
sendCommand(client, String.format("LOGIN %s", event.username()));
}
private void handleSendLogout(NetworkEvents.SendLogout event) {
NetworkingClient client = this.networkClients.get(event.clientId());
sendCommand(client, "LOGOUT");
}
private void handleSendGetPlayerlist(NetworkEvents.SendGetPlayerlist event) {
NetworkingClient client = this.networkClients.get(event.clientId());
sendCommand(client, "GET PLAYERLIST");
}
private void handleSendGetGamelist(NetworkEvents.SendGetGamelist event) {
NetworkingClient client = this.networkClients.get(event.clientId());
sendCommand(client, "GET GAMELIST");
}
private void handleSendSubscribe(NetworkEvents.SendSubscribe event) {
NetworkingClient client = this.networkClients.get(event.clientId());
sendCommand(client, String.format("SUBSCRIBE %s", event.gameType()));
}
private void handleSendMove(NetworkEvents.SendMove event) {
NetworkingClient client = this.networkClients.get(event.clientId());
sendCommand(client, String.format("MOVE %d", event.moveNumber()));
}
private void handleSendChallenge(NetworkEvents.SendChallenge event) {
NetworkingClient client = this.networkClients.get(event.clientId());
sendCommand(client, String.format("CHALLENGE %s %s", event.usernameToChallenge(), event.gameType()));
}
private void handleSendAcceptChallenge(NetworkEvents.SendAcceptChallenge event) {
NetworkingClient client = this.networkClients.get(event.clientId());
sendCommand(client, String.format("CHALLENGE ACCEPT %d", event.challengeId()));
}
private void handleSendForfeit(NetworkEvents.SendForfeit event) {
NetworkingClient client = this.networkClients.get(event.clientId());
sendCommand(client, "FORFEIT");
}
private void handleSendMessage(NetworkEvents.SendMessage event) {
NetworkingClient client = this.networkClients.get(event.clientId());
sendCommand(client, String.format("MESSAGE %s", event.message()));
}
private void handleSendHelp(NetworkEvents.SendHelp event) {
NetworkingClient client = this.networkClients.get(event.clientId());
sendCommand(client, "HELP");
}
private void handleSendHelpForCommand(NetworkEvents.SendHelpForCommand event) {
NetworkingClient client = this.networkClients.get(event.clientId());
sendCommand(client, String.format("HELP %s", event.command()));
}
private void sendCommand(NetworkingClient client, String command) {
logger.info("Preparing to send command: {} to server: {}:{}. clientId: {}",
command.trim(), client.getHost(), client.getPort(), client.getId());
client.writeAndFlushnl(command);
}
private void handleChangeClientHost(NetworkEvents.ChangeClientHost event) {
NetworkingClient client = this.networkClients.get(event.clientId());
client.closeConnection();
startClientRequest(event.ip(), event.port(), event.clientId());
}
private void handleCloseClient(NetworkEvents.CloseClient event) {
NetworkingClient client = this.networkClients.get(event.connectionId());
NetworkingClient client = this.networkClients.get(event.clientId());
client.closeConnection(); // TODO: Check if not blocking, what if error, mb not remove?
this.networkClients.remove(event.connectionId());
logger.info("Client {} closed successfully.", event.connectionId());
this.networkClients.remove(event.clientId());
logger.info("Client {} closed successfully.", event.clientId());
}
// private void handleReconnect(Events.ServerEvents.Reconnect event) {
// NetworkingClient client = this.networkClients.get(event.connectionId());
// if (client != null) {
// try {
// client;
// logger.info("Server {} reconnected", event.connectionId());
// } catch (Exception e) {
// logger.error("Server {} failed to reconnect", event.connectionId(), e);
// GlobalEventBus.post(new Events.ServerEvents.CouldNotConnect(event.connectionId()));
// }
// }
// } // TODO: Reconnect on disconnect
// private void handleChangeConnection(Events.ServerEvents.ChangeConnection event) {
// ServerConnection serverConnection = this.serverConnections.get(event.connectionId());
// if (serverConnection != null) {
// try {
// serverConnection.connect(event.ip(), event.port());
// logger.info("Server {} changed connection to {}:{}", event.connectionId(),
// event.ip(), event.port());
// } catch (Exception e) {
// logger.error("Server {} failed to change connection", event.connectionId(),
// e);
// GlobalEventBus.post(new
// Events.ServerEvents.CouldNotConnect(event.connectionId()));
// }
// }
// } TODO
private void getAllConnections(NetworkEvents.RequestsAllClients request) {
private void handleGetAllConnections(NetworkEvents.RequestsAllClients request) {
List<NetworkingClient> a = new ArrayList<>(this.networkClients.values());
request.future().complete(a);
}
public void shutdownAll(NetworkEvents.ForceCloseAllClients request) {
public void handleShutdownAll(NetworkEvents.ForceCloseAllClients request) {
this.networkClients.values().forEach(NetworkingClient::closeConnection);
this.networkClients.clear();
logger.info("All servers shut down");

View File

@@ -2,13 +2,11 @@ package org.toop.framework.networking;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import jdk.jfr.Event;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.framework.eventbus.EventFlow;
import org.toop.framework.networking.events.NetworkEvents;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -44,23 +42,147 @@ public class NetworkingGameClientHandler extends ChannelInboundHandlerAdapter {
}
private void parseServerReturn(String rec) {
if (rec.toLowerCase().contains("playerlist")) {
playerListHandler(rec);
} else if (rec.toLowerCase().contains("close")) {
} else {}
String recSrvRemoved = rec.substring("SVR ".length());
Pattern gamePattern = Pattern.compile("GAME (\\w+)", Pattern.CASE_INSENSITIVE);
Matcher gameMatch = gamePattern.matcher(recSrvRemoved);
if (gameMatch.find()) {
switch(gameMatch.group(1)) {
case "YOURTURN": gameYourTurnHandler(recSrvRemoved); return;
case "MOVE": gameMoveHandler(recSrvRemoved); return;
case "MATCH": gameMatchHandler(recSrvRemoved); return;
case "CHALLENGE": gameChallengeHandler(recSrvRemoved); return;
case "WIN",
"DRAW",
"LOSE": gameWinConditionHandler(recSrvRemoved); return;
default: return;
}
} else {
Pattern getPattern = Pattern.compile("(\\w+)", Pattern.CASE_INSENSITIVE);
Matcher getMatch = getPattern.matcher(recSrvRemoved);
if (getMatch.find()) {
switch(getMatch.group(1)) {
case "PLAYERLIST": playerlistHandler(recSrvRemoved); return;
case "GAMELIST": gamelistHandler(recSrvRemoved); return;
case "HELP": helpHandler(recSrvRemoved); return;
default: return;
}
} else {
return; // TODO: Should be an error.
}
}
}
private void playerListHandler(String rec) {
Pattern pattern = Pattern.compile("\"([^\"]+)\"");
String[] players = pattern.matcher(rec)
private void gameMoveHandler(String rec) {
String[] msg = Pattern
.compile("(?:player|details|move):\\s*\"?([^\",}]+)\"?", Pattern.CASE_INSENSITIVE)
.matcher(rec)
.results()
.map(m -> m.group(1).trim())
.toArray(String[]::new);
new EventFlow()
.addPostEvent(new NetworkEvents.GameMoveResponse(this.connectionId, msg[0], msg[1], msg[2]))
.asyncPostEvent();
}
private void gameWinConditionHandler(String rec) {
String condition = Pattern
.compile("\\b(win|draw|lose)\\b", Pattern.CASE_INSENSITIVE)
.matcher(rec)
.results()
.toString()
.trim();
new EventFlow()
.addPostEvent(new NetworkEvents.GameResultResponse(this.connectionId, condition))
.asyncPostEvent();
}
private void gameChallengeHandler(String rec) {
boolean isCancelled = rec.toLowerCase().startsWith("challenge accepted");
try {
String[] msg = Pattern
.compile("(?:CHALLENGER|GAMETYPE|CHALLENGENUMBER):\\s*\"?(.*?)\"?\\s*(?:,|})")
.matcher(rec)
.results()
.map(m -> m.group(1))
.map(m -> m.group().trim())
.toArray(String[]::new);
new EventFlow()
.addPostEvent(new NetworkEvents.PlayerListResponse(this.connectionId, players))
if (isCancelled) new EventFlow()
.addPostEvent(new NetworkEvents.ChallengeCancelledResponse(this.connectionId, msg[0]))
.asyncPostEvent();
else new EventFlow()
.addPostEvent(new NetworkEvents.ChallengeResponse(this.connectionId, msg[0], msg[1], msg[2]))
.asyncPostEvent();
} catch (ArrayIndexOutOfBoundsException e) {
logger.error("Array out of bounds for: {}", rec, e);
}
}
private void gameMatchHandler(String rec) {
try {
String[] msg = Pattern
.compile("\"([^\"]*)\"")
.matcher(rec)
.results()
.map(m -> m.group(1).trim())
.toArray(String[]::new);
// [0] playerToMove, [1] gameType, [2] opponent
new EventFlow()
.addPostEvent(new NetworkEvents.GameMatchResponse(this.connectionId, msg[0], msg[1], msg[2]))
.asyncPostEvent();
} catch (ArrayIndexOutOfBoundsException e) {
logger.error("Array out of bounds for: {}", rec, e);
}
}
private void gameYourTurnHandler(String rec) {
String msg = Pattern
.compile("TURNMESSAGE:\\s*\"([^\"]*)\"")
.matcher(rec)
.results()
.toString()
.trim();
new EventFlow()
.addPostEvent(new NetworkEvents.YourTurnResponse(this.connectionId, msg))
.asyncPostEvent();
}
private void playerlistHandler(String rec) {
String[] players = Pattern
.compile("\"([^\"]+)\"")
.matcher(rec)
.results()
.map(m -> m.group(1).trim())
.toArray(String[]::new);
new EventFlow()
.addPostEvent(new NetworkEvents.PlayerlistResponse(this.connectionId, players))
.asyncPostEvent();
}
private void gamelistHandler(String rec) {
String[] gameTypes = Pattern
.compile("\"([^\"]+)\"")
.matcher(rec)
.results()
.map(m -> m.group(1).trim())
.toArray(String[]::new);
new EventFlow()
.addPostEvent(new NetworkEvents.GamelistResponse(this.connectionId, gameTypes))
.asyncPostEvent();
}
private void helpHandler(String rec) {
logger.info(rec);
}
@Override

View File

@@ -13,93 +13,107 @@ import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* A collection of networking-related event records for use with the {@link org.toop.framework.eventbus.GlobalEventBus}.
* <p>
* This class defines all the events that can be posted or listened to in the networking subsystem.
* Events are separated into those with unique IDs (EventWithSnowflake) and those without (EventWithoutSnowflake).
* </p>
*/
public class NetworkEvents extends EventsBase {
/**
* BLOCKING Requests all active connections. The result is returned via the provided
* CompletableFuture.
* Requests all active client connections.
* <p>
* This is a blocking event. The result will be delivered via the provided {@link CompletableFuture}.
* </p>
*
* @param future List of all connections in string form.
* @param future CompletableFuture to receive the list of active {@link NetworkingClient} instances.
*/
public record RequestsAllClients(CompletableFuture<List<NetworkingClient>> future) implements EventWithoutSnowflake {}
/** Forces closing all active connections immediately. */
/** Forces all active client connections to close immediately. */
public record ForceCloseAllClients() implements EventWithoutSnowflake {}
public record PlayerListResponse(long clientId, String[] playerlist) implements EventWithoutSnowflake {}
/** Response indicating a challenge was cancelled. */
public record ChallengeCancelledResponse(long clientId, String challengeId) implements EventWithoutSnowflake {}
public record CloseClient(long connectionId) implements EventWithoutSnowflake {}
/** Response indicating a challenge was received. */
public record ChallengeResponse(long clientId, String challengerName, String gameType, String challengeId)
implements EventWithoutSnowflake {}
/** Response containing a list of players for a client. */
public record PlayerlistResponse(long clientId, String[] playerlist) implements EventWithoutSnowflake {}
/** Response containing a list of games for a client. */
public record GamelistResponse(long clientId, String[] gamelist) implements EventWithoutSnowflake {}
/** Response indicating a game match information for a client. */
public record GameMatchResponse(long clientId, String playerToMove, String gameType, String opponent)
implements EventWithoutSnowflake {}
/** Response indicating the result of a game. */
public record GameResultResponse(long clientId, String condition) implements EventWithoutSnowflake {}
/** Response indicating a game move occurred. */
public record GameMoveResponse(long clientId, String player, String details, String move)
implements EventWithoutSnowflake {}
/** Response indicating it is the player's turn. */
public record YourTurnResponse(long clientId, String message) implements EventWithoutSnowflake {}
/** Request to send login credentials for a client. */
public record SendLogin(long clientId, String username) implements EventWithoutSnowflake {}
/** Request to log out a client. */
public record SendLogout(long clientId) implements EventWithoutSnowflake {}
/** Request to retrieve the player list for a client. */
public record SendGetPlayerlist(long clientId) implements EventWithoutSnowflake {}
/** Request to retrieve the game list for a client. */
public record SendGetGamelist(long clientId) implements EventWithoutSnowflake {}
/** Request to subscribe a client to a game type. */
public record SendSubscribe(long clientId, String gameType) implements EventWithoutSnowflake {}
/** Request to make a move in a game. */
public record SendMove(long clientId, short moveNumber) implements EventWithoutSnowflake {}
/** Request to challenge another player. */
public record SendChallenge(long clientId, String usernameToChallenge, String gameType)
implements EventWithoutSnowflake {}
/** Request to accept a challenge. */
public record SendAcceptChallenge(long clientId, int challengeId) implements EventWithoutSnowflake {}
/** Request to forfeit a game. */
public record SendForfeit(long clientId) implements EventWithoutSnowflake {}
/** Request to send a message from a client. */
public record SendMessage(long clientId, String message) implements EventWithoutSnowflake {}
/** Request to display help to a client. */
public record SendHelp(long clientId) implements EventWithoutSnowflake {}
/** Request to display help for a specific command. */
public record SendHelpForCommand(long clientId, String command) implements EventWithoutSnowflake {}
/** Request to close a specific client connection. */
public record CloseClient(long clientId) implements EventWithoutSnowflake {}
/**
* Event to start a new client connection to a server.
* Event to start a new client connection.
* <p>
* This event is typically posted to the {@code GlobalEventBus} to initiate the creation of
* a client connection, and carries all information needed to establish that connection:
* <br>
* - A factory for creating the Netty handler that will manage the connection
* <br>
* - The server's IP address and port
* <br>
* - A unique event identifier for correlation with follow-up events
* Carries IP, port, and a unique event ID for correlation with responses.
* </p>
*
* <p>
* The {@link #eventSnowflake()} allows callers to correlate the {@code StartClient} event
* with subsequent success/failure events. For example, a {@code StartClientSuccess}
* or {@code StartClientFailure} event may carry the same {@code eventId}.
* </p>
*
* @param ip The IP address of the server to connect to.
* @param port The port number of the server to connect to.
* @param eventSnowflake A unique identifier for this event, typically injected
* automatically by the {@link org.toop.framework.eventbus.EventFlow}.
* @param ip Server IP address.
* @param port Server port.
* @param eventSnowflake Unique event identifier for correlation.
*/
public record StartClient(
String ip,
int port,
long eventSnowflake
) implements EventWithSnowflake {
public record StartClient(String ip, int port, long eventSnowflake) implements EventWithSnowflake {
/**
* Returns a map representation of this event, where keys are record component names
* and values are their corresponding values. Useful for generic logging, debugging,
* or serializing events without hardcoding field names.
*
* @return a {@code Map<String, Object>} containing field names and values
*/
@Override
public Map<String, Object> result() {
return Stream.of(this.getClass().getRecordComponents())
.collect(Collectors.toMap(
RecordComponent::getName,
rc -> {
try {
return rc.getAccessor().invoke(this);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
));
}
/**
* Returns the unique event identifier used for correlating this event.
*
* @return the event ID string
*/
@Override
public long eventSnowflake() {
return this.eventSnowflake;
}
}
/**
*
* @param clientId The ID of the client to be used in requests.
* @param eventSnowflake The eventID used in checking if event is for you.
*/
public record StartClientResponse(long clientId, long eventSnowflake)
implements EventWithSnowflake {
@Override
public Map<String, Object> result() {
return Stream.of(this.getClass().getRecordComponents())
@@ -122,52 +136,67 @@ public class NetworkEvents extends EventsBase {
}
/**
* Response confirming a client was started.
*
* @param clientId The ID of the client that received the response.
* @param clientId The client ID assigned to the new connection.
* @param eventSnowflake Event ID used for correlation.
*/
public record StartClientResponse(long clientId, long eventSnowflake) implements EventWithSnowflake {
@Override
public Map<String, Object> result() {
return Stream.of(this.getClass().getRecordComponents())
.collect(Collectors.toMap(
RecordComponent::getName,
rc -> {
try {
return rc.getAccessor().invoke(this);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
));
}
@Override
public long eventSnowflake() {
return this.eventSnowflake;
}
}
/** Generic server response. */
public record ServerResponse(long clientId) implements EventWithoutSnowflake {}
/**
* Triggers sending a command to a server.
* Request to send a command to a server.
*
* @param connectionId The UUID of the connection to send the command on.
* @param clientId The client connection ID.
* @param args The command arguments.
*/
public record SendCommand(long connectionId, String... args) implements EventWithoutSnowflake {}
/**
* Triggers reconnecting to a previous address.
*
* @param connectionId The identifier of the connection being reconnected.
*/
public record Reconnect(long connectionId) implements EventWithoutSnowflake {}
public record SendCommand(long clientId, String... args) implements EventWithoutSnowflake {}
/** WIP (Not working) Request to reconnect a client to a previous address. */
public record Reconnect(long clientId) implements EventWithoutSnowflake {}
/**
* Triggers when the server client receives a message.
* Response triggered when a message is received from a server.
*
* @param ConnectionId The snowflake id of the connection that received the message.
* @param message The message received.
* @param clientId The connection ID that received the message.
* @param message The message content.
*/
public record ReceivedMessage(long ConnectionId, String message) implements EventWithoutSnowflake {}
public record ReceivedMessage(long clientId, String message) implements EventWithoutSnowflake {}
/**
* Triggers changing connection to a new address.
* Request to change a client connection to a new server.
*
* @param connectionId The identifier of the connection being changed.
* @param ip The new IP address.
* @param port The new port.
* @param clientId The client connection ID.
* @param ip The new server IP.
* @param port The new server port.
*/
public record ChangeClient(long connectionId, String ip, int port) implements EventWithoutSnowflake {}
public record ChangeClientHost(long clientId, String ip, int port) implements EventWithoutSnowflake {}
/** WIP (Not working) Response indicating that the client could not connect. */
public record CouldNotConnect(long clientId) implements EventWithoutSnowflake {}
/**
* Triggers when the server couldn't connect to the desired address.
*
* @param connectionId The identifier of the connection that failed.
*/
public record CouldNotConnect(long connectionId) implements EventWithoutSnowflake {}
/** WIP Triggers when a connection closes. */
public record ClosedConnection() implements EventWithoutSnowflake {}
/** Event indicating a client connection was closed. */
public record ClosedConnection(long clientId) implements EventWithoutSnowflake {}
}