mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-09-09 03:46:18 -04:00
feat(multiplayer): much simpler invitation code format.
This commit is contained in:
parent
1762e78cb3
commit
de421f93b5
@ -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 {
|
||||
|
@ -43,7 +43,7 @@ public class MultiplayerClient extends Thread {
|
||||
|
||||
private final EventManager<ConnectedEvent> onConnected = new EventManager<>();
|
||||
private final EventManager<Event> onDisconnected = new EventManager<>();
|
||||
private final EventManager<Event> onKicked = new EventManager<>();
|
||||
private final EventManager<KickEvent> onKicked = new EventManager<>();
|
||||
private final EventManager<Event> onHandshake = new EventManager<>();
|
||||
|
||||
public MultiplayerClient(String id, int port) {
|
||||
@ -70,7 +70,7 @@ public class MultiplayerClient extends Thread {
|
||||
return onDisconnected;
|
||||
}
|
||||
|
||||
public EventManager<Event> onKicked() {
|
||||
public EventManager<KickEvent> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<CatoSession> startCato(String sessionName, String token, State state) {
|
||||
private static CompletableFuture<CatoSession> 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<CatoSession> 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<CatoSession> 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<CatoSession> 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<CatoSession> 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<CatoSession> 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("^(?<id>(idx|mix)(.*?))#(?<port>\\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<CatoIdEvent> onIdGenerated = new EventManager<>();
|
||||
private final EventManager<Event> 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<String> commands) {
|
||||
CatoSession(State type, Process process, List<String> 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;
|
||||
}
|
||||
|
@ -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<Throwable, String> fallback) {
|
||||
Throwable e = resolveException(t);
|
||||
if (e instanceof CancellationException) {
|
||||
|
@ -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<String, Endpoint> clients = new ConcurrentHashMap<>();
|
||||
private final Map<String, Endpoint> 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()));
|
||||
|
@ -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?
|
||||
|
@ -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=退出房間後,你將會與伺服器斷開連接,是否繼續?
|
||||
|
@ -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=退出房间后,你将会与服务器断开连接,是否继续?
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user