From de421f93b5d93c7e277d92e3ceb601bcff9da21f Mon Sep 17 00:00:00 2001 From: huanghongxun Date: Wed, 20 Oct 2021 00:25:45 +0800 Subject: [PATCH] feat(multiplayer): much simpler invitation code format. --- .../ui/multiplayer/MultiplayerChannel.java | 12 +++- .../ui/multiplayer/MultiplayerClient.java | 29 ++++++++-- .../ui/multiplayer/MultiplayerManager.java | 57 +++++++------------ .../hmcl/ui/multiplayer/MultiplayerPage.java | 21 +++++-- .../ui/multiplayer/MultiplayerServer.java | 22 +++++-- .../resources/assets/lang/I18N.properties | 7 ++- .../resources/assets/lang/I18N_zh.properties | 7 ++- .../assets/lang/I18N_zh_CN.properties | 7 ++- .../MultiplayerClientServerTest.java | 2 +- 9 files changed, 104 insertions(+), 60 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerChannel.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerChannel.java index b8fd29d88..fc1888408 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerChannel.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerChannel.java @@ -87,12 +87,18 @@ public final class MultiplayerChannel { } public static class JoinResponse extends Response { + private final String sessionName; private final int port; - public JoinResponse(int port) { + public JoinResponse(String sessionName, int port) { + this.sessionName = sessionName; this.port = port; } + public String getSessionName() { + return sessionName; + } + public int getPort() { return port; } @@ -120,6 +126,10 @@ public final class MultiplayerChannel { public String getMsg() { return msg; } + + public static final String VERSION_NOT_MATCHED = "version_not_matched"; + public static final String KICKED = "kicked"; + public static final String JOIN_ACEEPTANCE_TIMEOUT = "join_acceptance_timeout"; } public static class CatoClient extends Event { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerClient.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerClient.java index 7772fb432..2edfeb4a9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerClient.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerClient.java @@ -43,7 +43,7 @@ public class MultiplayerClient extends Thread { private final EventManager onConnected = new EventManager<>(); private final EventManager onDisconnected = new EventManager<>(); - private final EventManager onKicked = new EventManager<>(); + private final EventManager onKicked = new EventManager<>(); private final EventManager onHandshake = new EventManager<>(); public MultiplayerClient(String id, int port) { @@ -70,7 +70,7 @@ public class MultiplayerClient extends Thread { return onDisconnected; } - public EventManager onKicked() { + public EventManager onKicked() { return onKicked; } @@ -125,12 +125,12 @@ public class MultiplayerClient extends Thread { connected = true; - onConnected.fireEvent(new ConnectedEvent(this, joinResponse.getPort())); + onConnected.fireEvent(new ConnectedEvent(this, joinResponse.getSessionName(), joinResponse.getPort())); LOG.fine("Received join response with port " + joinResponse.getPort()); } else if (response instanceof KickResponse) { LOG.fine("Kicked by the server"); - onKicked.fireEvent(new Event(this)); + onKicked.fireEvent(new KickEvent(this, ((KickResponse) response).getMsg())); return; } else if (response instanceof KeepAliveResponse) { } else if (response instanceof HandshakeResponse) { @@ -193,15 +193,34 @@ public class MultiplayerClient extends Thread { } public static class ConnectedEvent extends Event { + private final String sessionName; private final int port; - public ConnectedEvent(Object source, int port) { + public ConnectedEvent(Object source, String sessionName, int port) { super(source); + this.sessionName = sessionName; this.port = port; } + public String getSessionName() { + return sessionName; + } + public int getPort() { return port; } } + + public static class KickEvent extends Event { + private final String reason; + + public KickEvent(Object source, String reason) { + super(source); + this.reason = reason; + } + + public String getReason() { + return reason; + } + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerManager.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerManager.java index 7c9333561..5ac561180 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerManager.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerManager.java @@ -27,7 +27,6 @@ import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.StringUtils; -import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.io.NetworkUtils; import org.jackhuang.hmcl.util.platform.Architecture; import org.jackhuang.hmcl.util.platform.CommandBuilder; @@ -87,7 +86,7 @@ public final class MultiplayerManager { return Metadata.HMCL_DIRECTORY.resolve("libraries").resolve(CATO_PATH); } - private static CompletableFuture startCato(String sessionName, String token, State state) { + private static CompletableFuture startCato(String token, State state) { return CompletableFuture.completedFuture(null).thenApplyAsync(unused -> { Path exe = getCatoExecutable(); if (!Files.isRegularFile(exe)) { @@ -108,18 +107,14 @@ public final class MultiplayerManager { throw new UncheckedIOException(e); } - return new CatoSession(sessionName, state, process, Arrays.asList(commands)); + return new CatoSession(state, process, Arrays.asList(commands)); }); } - public static CompletableFuture joinSession(String token, String version, String sessionName, String peer, Mode mode, int remotePort, int localPort, JoinSessionHandler handler) throws IncompatibleCatoVersionException { - if (!CATO_VERSION.equals(version)) { - throw new IncompatibleCatoVersionException(version, CATO_VERSION); - } + public static CompletableFuture joinSession(String token, String peer, Mode mode, int remotePort, int localPort, JoinSessionHandler handler) throws IncompatibleCatoVersionException { + LOG.info(String.format("Joining session (token=%s,peer=%s,mode=%s,remotePort=%d,localPort=%d)", token, peer, mode, remotePort, localPort)); - LOG.info(String.format("Joining session (token=%s,version=%s,sessionName=%s,peer=%s,mode=%s,remotePort=%d,localPort=%d)", token, version, sessionName, peer, mode, remotePort, localPort)); - - return startCato(sessionName, token, State.SLAVE).thenComposeAsync(wrap(session -> { + return startCato(token, State.SLAVE).thenComposeAsync(wrap(session -> { CompletableFuture future = new CompletableFuture<>(); session.forwardPort(peer, LOCAL_ADDRESS, localPort, REMOTE_ADDRESS, remotePort, mode); @@ -159,6 +154,7 @@ public final class MultiplayerManager { int port = findAvailablePort(); session.forwardPort(peer, LOCAL_ADDRESS, port, REMOTE_ADDRESS, connectedEvent.getPort(), mode); session.addRelatedThread(Lang.thread(new LocalServerBroadcaster(port, session), "LocalServerBroadcaster", true)); + session.setName(connectedEvent.getSessionName()); client.setGamePort(port); session.onExit.unregister(onExit); future.complete(session); @@ -194,10 +190,10 @@ public final class MultiplayerManager { public static CompletableFuture createSession(String token, String sessionName, int gamePort, boolean allowAllJoinRequests) { LOG.info(String.format("Creating session (token=%s,sessionName=%s,gamePort=%d)", token, sessionName, gamePort)); - return startCato(sessionName, token, State.MASTER).thenComposeAsync(wrap(session -> { + return startCato(token, State.MASTER).thenComposeAsync(wrap(session -> { CompletableFuture future = new CompletableFuture<>(); - MultiplayerServer server = new MultiplayerServer(gamePort, allowAllJoinRequests); + MultiplayerServer server = new MultiplayerServer(sessionName, gamePort, allowAllJoinRequests); server.startServer(); session.allowForwardingAddress(REMOTE_ADDRESS, server.getPort()); @@ -235,9 +231,12 @@ public final class MultiplayerManager { })); } + public static final Pattern INVITATION_CODE_PATTERN = Pattern.compile("^(?(idx|mix)(.*?))#(?\\d{2,5})$"); + public static Invitation parseInvitationCode(String invitationCode) throws JsonParseException { - String json = new String(Base64.getDecoder().decode(invitationCode), StandardCharsets.UTF_8); - return JsonUtils.fromNonNullJson(json, Invitation.class); + Matcher matcher = INVITATION_CODE_PATTERN.matcher(invitationCode); + if (!matcher.find()) throw new IllegalArgumentException("Invalid invitation code"); + return new Invitation(matcher.group("id"), Integer.parseInt(matcher.group("port"))); } public static int findAvailablePort() throws IOException { @@ -300,7 +299,7 @@ public final class MultiplayerManager { private final EventManager onIdGenerated = new EventManager<>(); private final EventManager onPeerConnected = new EventManager<>(); - private final String name; + private String name; private final State type; private String id; private boolean peerConnected = false; @@ -308,14 +307,13 @@ public final class MultiplayerManager { private MultiplayerServer server; private final BufferedWriter writer; - CatoSession(String name, State type, Process process, List commands) { + CatoSession(State type, Process process, List commands) { super(process, commands); Runtime.getRuntime().addShutdownHook(new Thread(this::stop)); - LOG.info("Started cato with command: " + new CommandBuilder().addAll(commands).toString()); + LOG.info("Started cato with command: " + new CommandBuilder().addAll(commands)); - this.name = name; this.type = type; addRelatedThread(Lang.thread(this::waitFor, "CatoExitWaiter", true)); addRelatedThread(Lang.thread(new StreamPump(process.getInputStream(), this::checkCatoLog), "CatoInputStreamPump", true)); @@ -386,6 +384,10 @@ public final class MultiplayerManager { return name; } + public void setName(String name) { + this.name = name; + } + public State getType() { return type; } @@ -399,8 +401,7 @@ public final class MultiplayerManager { if (id == null) { throw new IllegalStateException("id not generated"); } - String json = JsonUtils.UGLY_GSON.toJson(new Invitation(CATO_VERSION, id, name, serverPort)); - return Base64.getEncoder().encodeToString(json.getBytes(StandardCharsets.UTF_8)); + return id + "#" + serverPort; } public synchronized void invokeCommand(String command) throws IOException { @@ -476,33 +477,19 @@ public final class MultiplayerManager { } public static class Invitation { - @SerializedName("v") - private final String version; private final String id; - @SerializedName("n") - private final String sessionName; @SerializedName("p") private final int channelPort; - public Invitation(String version, String id, String sessionName, int channelPort) { - this.version = version; + public Invitation(String id, int channelPort) { this.id = id; - this.sessionName = sessionName; this.channelPort = channelPort; } - public String getVersion() { - return version; - } - public String getId() { return id; } - public String getSessionName() { - return sessionName; - } - public int getChannelPort() { return channelPort; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPage.java index fe91f80ad..fc3818596 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPage.java @@ -48,6 +48,7 @@ import java.util.logging.Level; import static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig; import static org.jackhuang.hmcl.ui.FXUtils.runInFX; +import static org.jackhuang.hmcl.ui.multiplayer.MultiplayerChannel.KickResponse.*; import static org.jackhuang.hmcl.util.Lang.resolveException; import static org.jackhuang.hmcl.util.Logging.LOG; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; @@ -220,7 +221,7 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP session.getServer().setOnClientAdding((client, resolveClient, rejectClient) -> { runInFX(() -> { Controllers.dialog(new MessageDialogPane.Builder(i18n("multiplayer.session.create.join.prompt", client.getUsername()), i18n("multiplayer.session.create.join"), MessageDialogPane.MessageType.INFO) - .yesOrNo(resolveClient, () -> rejectClient.accept(i18n("multiplayer.session.join.wait_timeout"))) + .yesOrNo(resolveClient, () -> rejectClient.accept(MultiplayerChannel.KickResponse.JOIN_ACEEPTANCE_TIMEOUT)) .cancelOnTimeout(30 * 1000) .build()); }); @@ -278,8 +279,6 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP try { MultiplayerManager.joinSession( globalConfig().getMultiplayerToken(), - invitation.getVersion(), - invitation.getSessionName(), invitation.getId(), globalConfig().isMultiplayerRelay() && (StringUtils.isNotBlank(globalConfig().getMultiplayerToken()) || StringUtils.isNotBlank(System.getProperty("hmcl.multiplayer.relay"))) ? MultiplayerManager.Mode.BRIDGE @@ -307,10 +306,10 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP }); }); - session.getClient().onKicked().register(() -> { + session.getClient().onKicked().register(kickedEvent -> { runInFX(() -> { kicked.set(true); - Controllers.dialog(i18n("multiplayer.session.join.kicked")); + Controllers.dialog(i18n("multiplayer.session.join.kicked", localizeKickMessage(kickedEvent.getReason()))); }); }); @@ -330,6 +329,18 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP .addQuestion(new PromptDialogPane.Builder.StringQuestion(i18n("multiplayer.session.join.invitation_code"), "", new RequiredValidator()))); } + private String localizeKickMessage(String message) { + if (VERSION_NOT_MATCHED.equals(message)) { + return i18n("multiplayer.session.join.kicked.version_not_matched"); + } else if (KICKED.equals(message)) { + return i18n("multiplayer.session.join.kicked.kicked"); + } else if (JOIN_ACEEPTANCE_TIMEOUT.equals(message)) { + return i18n("multiplayer.session.join.kicked.join_acceptance_timeout"); + } else { + return message; + } + } + private String localizeErrorMessage(Throwable t, boolean isStaticToken, Function fallback) { Throwable e = resolveException(t); if (e instanceof CancellationException) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerServer.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerServer.java index 6794bb283..d9a1279c3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerServer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerServer.java @@ -29,6 +29,7 @@ import java.net.ServerSocket; import java.net.Socket; import java.nio.charset.StandardCharsets; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; @@ -37,6 +38,7 @@ import static org.jackhuang.hmcl.util.Logging.LOG; public class MultiplayerServer extends Thread { private ServerSocket socket; + private final String sessionName; private final int gamePort; private final boolean allowAllJoinRequests; @@ -49,7 +51,8 @@ public class MultiplayerServer extends Thread { private final Map clients = new ConcurrentHashMap<>(); private final Map nameClientMap = new ConcurrentHashMap<>(); - public MultiplayerServer(int gamePort, boolean allowAllJoinRequests) { + public MultiplayerServer(String sessionName, int gamePort, boolean allowAllJoinRequests) { + this.sessionName = sessionName; this.gamePort = gamePort; this.allowAllJoinRequests = allowAllJoinRequests; @@ -117,7 +120,7 @@ public class MultiplayerServer extends Thread { try { if (client.socket.isConnected()) { - client.write(new KickResponse("")); + client.write(new KickResponse(KickResponse.KICKED)); client.socket.close(); } } catch (IOException e) { @@ -150,6 +153,17 @@ public class MultiplayerServer extends Thread { LOG.info("Received join request with clientVersion=" + joinRequest.getClientVersion() + ", id=" + joinRequest.getUsername()); clientName = joinRequest.getUsername(); + if (!Objects.equals(MultiplayerManager.CATO_VERSION, joinRequest.getClientVersion())) { + try { + endpoint.write(new KickResponse(KickResponse.VERSION_NOT_MATCHED)); + LOG.info("Rejected join request from id=" + joinRequest.getUsername()); + socket.close(); + } catch (IOException e) { + LOG.log(Level.WARNING, "Failed to send kick response.", e); + return; + } + } + CatoClient catoClient = new CatoClient(this, clientName); nameClientMap.put(clientName, endpoint); onClientAdded.fireEvent(catoClient); @@ -157,7 +171,7 @@ public class MultiplayerServer extends Thread { if (onClientAdding != null && !allowAllJoinRequests) { onClientAdding.call(catoClient, () -> { try { - endpoint.write(new JoinResponse(gamePort)); + endpoint.write(new JoinResponse(sessionName, gamePort)); } catch (IOException e) { LOG.log(Level.WARNING, "Failed to send join response.", e); try { @@ -178,7 +192,7 @@ public class MultiplayerServer extends Thread { }); } else { // Allow all join requests. - endpoint.write(new JoinResponse(gamePort)); + endpoint.write(new JoinResponse(sessionName, gamePort)); } } else if (request instanceof KeepAliveRequest) { endpoint.write(new KeepAliveResponse(System.currentTimeMillis())); diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index dcb007e35..7f2b07c86 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -702,13 +702,14 @@ multiplayer.session.join.error.connection=Failed to join multiplayer session. Ca multiplayer.session.join.hint=You must obtain the invitation code from the gamer who has already created a multiplayer session. multiplayer.session.join.invitation_code=Invitation code multiplayer.session.join.invitation_code.error=Incorrect invitation code. Please obtain invitation code from the player who creates the multiplayer session. -multiplayer.session.join.invitation_code.version=Versions of multiplayer functionalities are not the same among you. -multiplayer.session.join.kicked=You have been kicked by the session holder. You will lost connection with the multiplayer session. +multiplayer.session.join.kicked=Lost connection: %s. +multiplayer.session.join.kicked.join_acceptance_timeout=The session holder does not accept your join request in time. +multiplayer.session.join.kicked.kicked=You have been kicked by the session holder. +multiplayer.session.join.kicked.version_not_matched=Versions of multiplayer functionalities are not the same among you. multiplayer.session.join.lost_connection=Lost connection with the multiplayer session. Maybe the session is destroyed by the creator, or you cannot establish connection with this session. multiplayer.session.join.port.error=Cannot find available local network port for listening. Please ensure that HMCL has the permission to listen on a port. multiplayer.session.join.rejected=Your connection is rejected by the session holder. multiplayer.session.join.wait=Waiting for acceptation. -multiplayer.session.join.wait_timeout=The session holder does not accept your join request in time. multiplayer.session.members=Room Members multiplayer.session.quit=Quit Room multiplayer.session.quit.warning=After quiting room, you will lost the connection with the server. Continue? diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index be358c7b9..511c31992 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -701,13 +701,14 @@ multiplayer.session.join.error.connection=加入房間失敗。無法與對方 multiplayer.session.join.hint=你需要向已經創建好房間的玩家索要邀請碼以便加入多人聯機房間 multiplayer.session.join.invitation_code=邀請碼 multiplayer.session.join.invitation_code.error=邀請碼不正確,請向開服玩家獲取邀請碼 -multiplayer.session.join.invitation_code.version=多人聯機功能版本號不一致,請保證連接多人聯機功能版本號一致。 -multiplayer.session.join.kicked=你已經被房主踢出房間,你將與房間失去連接。 +multiplayer.session.join.kicked=你與房間失去連接:%s。 +multiplayer.session.join.kicked.join_acceptance_timeout=對方未能即時同意你的加入申請 +multiplayer.session.join.kicked.kicked=你被房主踢出房間。 +multiplayer.session.join.kicked.version_not_matched=多人聯機功能版本號不一致,請保證連接多人聯機功能版本號一致。 multiplayer.session.join.lost_connection=你已與房間失去連接。這可能意味著房主已經解散房間,或者你無法連接至房間。 multiplayer.session.join.port.error=無法找到可用的本地網路埠,請確保 HMCL 擁有綁定本地埠的權限。 multiplayer.session.join.rejected=你被房主拒絕連接。 multiplayer.session.join.wait=等待對方同意加入申請。 -multiplayer.session.join.wait_timeout=對方未能即時同意你的加入申請 multiplayer.session.members=房間成員 multiplayer.session.quit=退出房間 multiplayer.session.quit.warning=退出房間後,你將會與伺服器斷開連接,是否繼續? diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 7ae197859..f41cb119d 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -701,13 +701,14 @@ multiplayer.session.join.error.connection=加入房间失败。无法与对方 multiplayer.session.join.hint=你需要向已经创建好房间的玩家索要邀请码以便加入多人联机房间 multiplayer.session.join.invitation_code=邀请码 multiplayer.session.join.invitation_code.error=邀请码不正确,请向开服玩家获取邀请码 -multiplayer.session.join.invitation_code.version=多人联机功能版本号不一致,请保证连接多人联机功能版本号一致。 -multiplayer.session.join.kicked=你已经被房主踢出房间,你将与房间失去连接。 +multiplayer.session.join.kicked=你与房间失去连接:%s。 +multiplayer.session.join.kicked.join_acceptance_timeout=对方未能即时同意你的加入申请 +multiplayer.session.join.kicked.kicked=你被房主踢出房间。 +multiplayer.session.join.kicked.version_not_matched=多人联机功能版本号不一致,请保证连接多人联机功能版本号一致。 multiplayer.session.join.lost_connection=你已与房间失去连接。这可能意味着房主已经解散房间,或者你无法连接至房间。 multiplayer.session.join.port.error=无法找到可用的本地网络端口,请确保 HMCL 拥有绑定本地端口的权限。 multiplayer.session.join.rejected=你被房主拒绝连接。 multiplayer.session.join.wait=等待对方同意加入申请。 -multiplayer.session.join.wait_timeout=对方未能即时同意你的加入申请 multiplayer.session.members=房间成员 multiplayer.session.quit=退出房间 multiplayer.session.quit.warning=退出房间后,你将会与服务器断开连接,是否继续? diff --git a/HMCL/src/test/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerClientServerTest.java b/HMCL/src/test/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerClientServerTest.java index 47a4f2faa..b90ac60e3 100644 --- a/HMCL/src/test/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerClientServerTest.java +++ b/HMCL/src/test/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerClientServerTest.java @@ -31,7 +31,7 @@ public class MultiplayerClientServerTest { public void startServer() throws Exception { Logging.initForTest(); int localPort = MultiplayerManager.findAvailablePort(); - MultiplayerServer server = new MultiplayerServer(1000, true); + MultiplayerServer server = new MultiplayerServer("SessionName", 1000, true); server.startServer(localPort); MultiplayerClient client = new MultiplayerClient("username", localPort);