mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-09-10 12:26:16 -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 {
|
public static class JoinResponse extends Response {
|
||||||
|
private final String sessionName;
|
||||||
private final int port;
|
private final int port;
|
||||||
|
|
||||||
public JoinResponse(int port) {
|
public JoinResponse(String sessionName, int port) {
|
||||||
|
this.sessionName = sessionName;
|
||||||
this.port = port;
|
this.port = port;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getSessionName() {
|
||||||
|
return sessionName;
|
||||||
|
}
|
||||||
|
|
||||||
public int getPort() {
|
public int getPort() {
|
||||||
return port;
|
return port;
|
||||||
}
|
}
|
||||||
@ -120,6 +126,10 @@ public final class MultiplayerChannel {
|
|||||||
public String getMsg() {
|
public String getMsg() {
|
||||||
return msg;
|
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 {
|
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<ConnectedEvent> onConnected = new EventManager<>();
|
||||||
private final EventManager<Event> onDisconnected = 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<>();
|
private final EventManager<Event> onHandshake = new EventManager<>();
|
||||||
|
|
||||||
public MultiplayerClient(String id, int port) {
|
public MultiplayerClient(String id, int port) {
|
||||||
@ -70,7 +70,7 @@ public class MultiplayerClient extends Thread {
|
|||||||
return onDisconnected;
|
return onDisconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EventManager<Event> onKicked() {
|
public EventManager<KickEvent> onKicked() {
|
||||||
return onKicked;
|
return onKicked;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,12 +125,12 @@ public class MultiplayerClient extends Thread {
|
|||||||
|
|
||||||
connected = true;
|
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());
|
LOG.fine("Received join response with port " + joinResponse.getPort());
|
||||||
} else if (response instanceof KickResponse) {
|
} else if (response instanceof KickResponse) {
|
||||||
LOG.fine("Kicked by the server");
|
LOG.fine("Kicked by the server");
|
||||||
onKicked.fireEvent(new Event(this));
|
onKicked.fireEvent(new KickEvent(this, ((KickResponse) response).getMsg()));
|
||||||
return;
|
return;
|
||||||
} else if (response instanceof KeepAliveResponse) {
|
} else if (response instanceof KeepAliveResponse) {
|
||||||
} else if (response instanceof HandshakeResponse) {
|
} else if (response instanceof HandshakeResponse) {
|
||||||
@ -193,15 +193,34 @@ public class MultiplayerClient extends Thread {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class ConnectedEvent extends Event {
|
public static class ConnectedEvent extends Event {
|
||||||
|
private final String sessionName;
|
||||||
private final int port;
|
private final int port;
|
||||||
|
|
||||||
public ConnectedEvent(Object source, int port) {
|
public ConnectedEvent(Object source, String sessionName, int port) {
|
||||||
super(source);
|
super(source);
|
||||||
|
this.sessionName = sessionName;
|
||||||
this.port = port;
|
this.port = port;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getSessionName() {
|
||||||
|
return sessionName;
|
||||||
|
}
|
||||||
|
|
||||||
public int getPort() {
|
public int getPort() {
|
||||||
return port;
|
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.task.Task;
|
||||||
import org.jackhuang.hmcl.util.Lang;
|
import org.jackhuang.hmcl.util.Lang;
|
||||||
import org.jackhuang.hmcl.util.StringUtils;
|
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.io.NetworkUtils;
|
||||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||||
import org.jackhuang.hmcl.util.platform.CommandBuilder;
|
import org.jackhuang.hmcl.util.platform.CommandBuilder;
|
||||||
@ -87,7 +86,7 @@ public final class MultiplayerManager {
|
|||||||
return Metadata.HMCL_DIRECTORY.resolve("libraries").resolve(CATO_PATH);
|
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 -> {
|
return CompletableFuture.completedFuture(null).thenApplyAsync(unused -> {
|
||||||
Path exe = getCatoExecutable();
|
Path exe = getCatoExecutable();
|
||||||
if (!Files.isRegularFile(exe)) {
|
if (!Files.isRegularFile(exe)) {
|
||||||
@ -108,18 +107,14 @@ public final class MultiplayerManager {
|
|||||||
throw new UncheckedIOException(e);
|
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 {
|
public static CompletableFuture<CatoSession> joinSession(String token, String peer, Mode mode, int remotePort, int localPort, JoinSessionHandler handler) throws IncompatibleCatoVersionException {
|
||||||
if (!CATO_VERSION.equals(version)) {
|
LOG.info(String.format("Joining session (token=%s,peer=%s,mode=%s,remotePort=%d,localPort=%d)", token, peer, mode, remotePort, localPort));
|
||||||
throw new IncompatibleCatoVersionException(version, CATO_VERSION);
|
|
||||||
}
|
|
||||||
|
|
||||||
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(token, State.SLAVE).thenComposeAsync(wrap(session -> {
|
||||||
|
|
||||||
return startCato(sessionName, token, State.SLAVE).thenComposeAsync(wrap(session -> {
|
|
||||||
CompletableFuture<CatoSession> future = new CompletableFuture<>();
|
CompletableFuture<CatoSession> future = new CompletableFuture<>();
|
||||||
|
|
||||||
session.forwardPort(peer, LOCAL_ADDRESS, localPort, REMOTE_ADDRESS, remotePort, mode);
|
session.forwardPort(peer, LOCAL_ADDRESS, localPort, REMOTE_ADDRESS, remotePort, mode);
|
||||||
@ -159,6 +154,7 @@ public final class MultiplayerManager {
|
|||||||
int port = findAvailablePort();
|
int port = findAvailablePort();
|
||||||
session.forwardPort(peer, LOCAL_ADDRESS, port, REMOTE_ADDRESS, connectedEvent.getPort(), mode);
|
session.forwardPort(peer, LOCAL_ADDRESS, port, REMOTE_ADDRESS, connectedEvent.getPort(), mode);
|
||||||
session.addRelatedThread(Lang.thread(new LocalServerBroadcaster(port, session), "LocalServerBroadcaster", true));
|
session.addRelatedThread(Lang.thread(new LocalServerBroadcaster(port, session), "LocalServerBroadcaster", true));
|
||||||
|
session.setName(connectedEvent.getSessionName());
|
||||||
client.setGamePort(port);
|
client.setGamePort(port);
|
||||||
session.onExit.unregister(onExit);
|
session.onExit.unregister(onExit);
|
||||||
future.complete(session);
|
future.complete(session);
|
||||||
@ -194,10 +190,10 @@ public final class MultiplayerManager {
|
|||||||
public static CompletableFuture<CatoSession> createSession(String token, String sessionName, int gamePort, boolean allowAllJoinRequests) {
|
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));
|
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<>();
|
CompletableFuture<CatoSession> future = new CompletableFuture<>();
|
||||||
|
|
||||||
MultiplayerServer server = new MultiplayerServer(gamePort, allowAllJoinRequests);
|
MultiplayerServer server = new MultiplayerServer(sessionName, gamePort, allowAllJoinRequests);
|
||||||
server.startServer();
|
server.startServer();
|
||||||
|
|
||||||
session.allowForwardingAddress(REMOTE_ADDRESS, server.getPort());
|
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 {
|
public static Invitation parseInvitationCode(String invitationCode) throws JsonParseException {
|
||||||
String json = new String(Base64.getDecoder().decode(invitationCode), StandardCharsets.UTF_8);
|
Matcher matcher = INVITATION_CODE_PATTERN.matcher(invitationCode);
|
||||||
return JsonUtils.fromNonNullJson(json, Invitation.class);
|
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 {
|
public static int findAvailablePort() throws IOException {
|
||||||
@ -300,7 +299,7 @@ public final class MultiplayerManager {
|
|||||||
private final EventManager<CatoIdEvent> onIdGenerated = new EventManager<>();
|
private final EventManager<CatoIdEvent> onIdGenerated = new EventManager<>();
|
||||||
private final EventManager<Event> onPeerConnected = new EventManager<>();
|
private final EventManager<Event> onPeerConnected = new EventManager<>();
|
||||||
|
|
||||||
private final String name;
|
private String name;
|
||||||
private final State type;
|
private final State type;
|
||||||
private String id;
|
private String id;
|
||||||
private boolean peerConnected = false;
|
private boolean peerConnected = false;
|
||||||
@ -308,14 +307,13 @@ public final class MultiplayerManager {
|
|||||||
private MultiplayerServer server;
|
private MultiplayerServer server;
|
||||||
private final BufferedWriter writer;
|
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);
|
super(process, commands);
|
||||||
|
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(this::stop));
|
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;
|
this.type = type;
|
||||||
addRelatedThread(Lang.thread(this::waitFor, "CatoExitWaiter", true));
|
addRelatedThread(Lang.thread(this::waitFor, "CatoExitWaiter", true));
|
||||||
addRelatedThread(Lang.thread(new StreamPump(process.getInputStream(), this::checkCatoLog), "CatoInputStreamPump", true));
|
addRelatedThread(Lang.thread(new StreamPump(process.getInputStream(), this::checkCatoLog), "CatoInputStreamPump", true));
|
||||||
@ -386,6 +384,10 @@ public final class MultiplayerManager {
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
public State getType() {
|
public State getType() {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
@ -399,8 +401,7 @@ public final class MultiplayerManager {
|
|||||||
if (id == null) {
|
if (id == null) {
|
||||||
throw new IllegalStateException("id not generated");
|
throw new IllegalStateException("id not generated");
|
||||||
}
|
}
|
||||||
String json = JsonUtils.UGLY_GSON.toJson(new Invitation(CATO_VERSION, id, name, serverPort));
|
return id + "#" + serverPort;
|
||||||
return Base64.getEncoder().encodeToString(json.getBytes(StandardCharsets.UTF_8));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void invokeCommand(String command) throws IOException {
|
public synchronized void invokeCommand(String command) throws IOException {
|
||||||
@ -476,33 +477,19 @@ public final class MultiplayerManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class Invitation {
|
public static class Invitation {
|
||||||
@SerializedName("v")
|
|
||||||
private final String version;
|
|
||||||
private final String id;
|
private final String id;
|
||||||
@SerializedName("n")
|
|
||||||
private final String sessionName;
|
|
||||||
@SerializedName("p")
|
@SerializedName("p")
|
||||||
private final int channelPort;
|
private final int channelPort;
|
||||||
|
|
||||||
public Invitation(String version, String id, String sessionName, int channelPort) {
|
public Invitation(String id, int channelPort) {
|
||||||
this.version = version;
|
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.sessionName = sessionName;
|
|
||||||
this.channelPort = channelPort;
|
this.channelPort = channelPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getVersion() {
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSessionName() {
|
|
||||||
return sessionName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getChannelPort() {
|
public int getChannelPort() {
|
||||||
return channelPort;
|
return channelPort;
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,7 @@ import java.util.logging.Level;
|
|||||||
|
|
||||||
import static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig;
|
import static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig;
|
||||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
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.Lang.resolveException;
|
||||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
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) -> {
|
session.getServer().setOnClientAdding((client, resolveClient, rejectClient) -> {
|
||||||
runInFX(() -> {
|
runInFX(() -> {
|
||||||
Controllers.dialog(new MessageDialogPane.Builder(i18n("multiplayer.session.create.join.prompt", client.getUsername()), i18n("multiplayer.session.create.join"), MessageDialogPane.MessageType.INFO)
|
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)
|
.cancelOnTimeout(30 * 1000)
|
||||||
.build());
|
.build());
|
||||||
});
|
});
|
||||||
@ -278,8 +279,6 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP
|
|||||||
try {
|
try {
|
||||||
MultiplayerManager.joinSession(
|
MultiplayerManager.joinSession(
|
||||||
globalConfig().getMultiplayerToken(),
|
globalConfig().getMultiplayerToken(),
|
||||||
invitation.getVersion(),
|
|
||||||
invitation.getSessionName(),
|
|
||||||
invitation.getId(),
|
invitation.getId(),
|
||||||
globalConfig().isMultiplayerRelay() && (StringUtils.isNotBlank(globalConfig().getMultiplayerToken()) || StringUtils.isNotBlank(System.getProperty("hmcl.multiplayer.relay")))
|
globalConfig().isMultiplayerRelay() && (StringUtils.isNotBlank(globalConfig().getMultiplayerToken()) || StringUtils.isNotBlank(System.getProperty("hmcl.multiplayer.relay")))
|
||||||
? MultiplayerManager.Mode.BRIDGE
|
? MultiplayerManager.Mode.BRIDGE
|
||||||
@ -307,10 +306,10 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
session.getClient().onKicked().register(() -> {
|
session.getClient().onKicked().register(kickedEvent -> {
|
||||||
runInFX(() -> {
|
runInFX(() -> {
|
||||||
kicked.set(true);
|
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())));
|
.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) {
|
private String localizeErrorMessage(Throwable t, boolean isStaticToken, Function<Throwable, String> fallback) {
|
||||||
Throwable e = resolveException(t);
|
Throwable e = resolveException(t);
|
||||||
if (e instanceof CancellationException) {
|
if (e instanceof CancellationException) {
|
||||||
|
@ -29,6 +29,7 @@ import java.net.ServerSocket;
|
|||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
@ -37,6 +38,7 @@ import static org.jackhuang.hmcl.util.Logging.LOG;
|
|||||||
|
|
||||||
public class MultiplayerServer extends Thread {
|
public class MultiplayerServer extends Thread {
|
||||||
private ServerSocket socket;
|
private ServerSocket socket;
|
||||||
|
private final String sessionName;
|
||||||
private final int gamePort;
|
private final int gamePort;
|
||||||
private final boolean allowAllJoinRequests;
|
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> clients = new ConcurrentHashMap<>();
|
||||||
private final Map<String, Endpoint> nameClientMap = 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.gamePort = gamePort;
|
||||||
this.allowAllJoinRequests = allowAllJoinRequests;
|
this.allowAllJoinRequests = allowAllJoinRequests;
|
||||||
|
|
||||||
@ -117,7 +120,7 @@ public class MultiplayerServer extends Thread {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (client.socket.isConnected()) {
|
if (client.socket.isConnected()) {
|
||||||
client.write(new KickResponse(""));
|
client.write(new KickResponse(KickResponse.KICKED));
|
||||||
client.socket.close();
|
client.socket.close();
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@ -150,6 +153,17 @@ public class MultiplayerServer extends Thread {
|
|||||||
LOG.info("Received join request with clientVersion=" + joinRequest.getClientVersion() + ", id=" + joinRequest.getUsername());
|
LOG.info("Received join request with clientVersion=" + joinRequest.getClientVersion() + ", id=" + joinRequest.getUsername());
|
||||||
clientName = 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);
|
CatoClient catoClient = new CatoClient(this, clientName);
|
||||||
nameClientMap.put(clientName, endpoint);
|
nameClientMap.put(clientName, endpoint);
|
||||||
onClientAdded.fireEvent(catoClient);
|
onClientAdded.fireEvent(catoClient);
|
||||||
@ -157,7 +171,7 @@ public class MultiplayerServer extends Thread {
|
|||||||
if (onClientAdding != null && !allowAllJoinRequests) {
|
if (onClientAdding != null && !allowAllJoinRequests) {
|
||||||
onClientAdding.call(catoClient, () -> {
|
onClientAdding.call(catoClient, () -> {
|
||||||
try {
|
try {
|
||||||
endpoint.write(new JoinResponse(gamePort));
|
endpoint.write(new JoinResponse(sessionName, gamePort));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.log(Level.WARNING, "Failed to send join response.", e);
|
LOG.log(Level.WARNING, "Failed to send join response.", e);
|
||||||
try {
|
try {
|
||||||
@ -178,7 +192,7 @@ public class MultiplayerServer extends Thread {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Allow all join requests.
|
// Allow all join requests.
|
||||||
endpoint.write(new JoinResponse(gamePort));
|
endpoint.write(new JoinResponse(sessionName, gamePort));
|
||||||
}
|
}
|
||||||
} else if (request instanceof KeepAliveRequest) {
|
} else if (request instanceof KeepAliveRequest) {
|
||||||
endpoint.write(new KeepAliveResponse(System.currentTimeMillis()));
|
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.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=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.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=Lost connection: %s.
|
||||||
multiplayer.session.join.kicked=You have been kicked by the session holder. You will lost connection with the multiplayer session.
|
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.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.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.rejected=Your connection is rejected by the session holder.
|
||||||
multiplayer.session.join.wait=Waiting for acceptation.
|
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.members=Room Members
|
||||||
multiplayer.session.quit=Quit Room
|
multiplayer.session.quit=Quit Room
|
||||||
multiplayer.session.quit.warning=After quiting room, you will lost the connection with the server. Continue?
|
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.hint=你需要向已經創建好房間的玩家索要邀請碼以便加入多人聯機房間
|
||||||
multiplayer.session.join.invitation_code=邀請碼
|
multiplayer.session.join.invitation_code=邀請碼
|
||||||
multiplayer.session.join.invitation_code.error=邀請碼不正確,請向開服玩家獲取邀請碼
|
multiplayer.session.join.invitation_code.error=邀請碼不正確,請向開服玩家獲取邀請碼
|
||||||
multiplayer.session.join.invitation_code.version=多人聯機功能版本號不一致,請保證連接多人聯機功能版本號一致。
|
multiplayer.session.join.kicked=你與房間失去連接:%s。
|
||||||
multiplayer.session.join.kicked=你已經被房主踢出房間,你將與房間失去連接。
|
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.lost_connection=你已與房間失去連接。這可能意味著房主已經解散房間,或者你無法連接至房間。
|
||||||
multiplayer.session.join.port.error=無法找到可用的本地網路埠,請確保 HMCL 擁有綁定本地埠的權限。
|
multiplayer.session.join.port.error=無法找到可用的本地網路埠,請確保 HMCL 擁有綁定本地埠的權限。
|
||||||
multiplayer.session.join.rejected=你被房主拒絕連接。
|
multiplayer.session.join.rejected=你被房主拒絕連接。
|
||||||
multiplayer.session.join.wait=等待對方同意加入申請。
|
multiplayer.session.join.wait=等待對方同意加入申請。
|
||||||
multiplayer.session.join.wait_timeout=對方未能即時同意你的加入申請
|
|
||||||
multiplayer.session.members=房間成員
|
multiplayer.session.members=房間成員
|
||||||
multiplayer.session.quit=退出房間
|
multiplayer.session.quit=退出房間
|
||||||
multiplayer.session.quit.warning=退出房間後,你將會與伺服器斷開連接,是否繼續?
|
multiplayer.session.quit.warning=退出房間後,你將會與伺服器斷開連接,是否繼續?
|
||||||
|
@ -701,13 +701,14 @@ multiplayer.session.join.error.connection=加入房间失败。无法与对方
|
|||||||
multiplayer.session.join.hint=你需要向已经创建好房间的玩家索要邀请码以便加入多人联机房间
|
multiplayer.session.join.hint=你需要向已经创建好房间的玩家索要邀请码以便加入多人联机房间
|
||||||
multiplayer.session.join.invitation_code=邀请码
|
multiplayer.session.join.invitation_code=邀请码
|
||||||
multiplayer.session.join.invitation_code.error=邀请码不正确,请向开服玩家获取邀请码
|
multiplayer.session.join.invitation_code.error=邀请码不正确,请向开服玩家获取邀请码
|
||||||
multiplayer.session.join.invitation_code.version=多人联机功能版本号不一致,请保证连接多人联机功能版本号一致。
|
multiplayer.session.join.kicked=你与房间失去连接:%s。
|
||||||
multiplayer.session.join.kicked=你已经被房主踢出房间,你将与房间失去连接。
|
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.lost_connection=你已与房间失去连接。这可能意味着房主已经解散房间,或者你无法连接至房间。
|
||||||
multiplayer.session.join.port.error=无法找到可用的本地网络端口,请确保 HMCL 拥有绑定本地端口的权限。
|
multiplayer.session.join.port.error=无法找到可用的本地网络端口,请确保 HMCL 拥有绑定本地端口的权限。
|
||||||
multiplayer.session.join.rejected=你被房主拒绝连接。
|
multiplayer.session.join.rejected=你被房主拒绝连接。
|
||||||
multiplayer.session.join.wait=等待对方同意加入申请。
|
multiplayer.session.join.wait=等待对方同意加入申请。
|
||||||
multiplayer.session.join.wait_timeout=对方未能即时同意你的加入申请
|
|
||||||
multiplayer.session.members=房间成员
|
multiplayer.session.members=房间成员
|
||||||
multiplayer.session.quit=退出房间
|
multiplayer.session.quit=退出房间
|
||||||
multiplayer.session.quit.warning=退出房间后,你将会与服务器断开连接,是否继续?
|
multiplayer.session.quit.warning=退出房间后,你将会与服务器断开连接,是否继续?
|
||||||
|
@ -31,7 +31,7 @@ public class MultiplayerClientServerTest {
|
|||||||
public void startServer() throws Exception {
|
public void startServer() throws Exception {
|
||||||
Logging.initForTest();
|
Logging.initForTest();
|
||||||
int localPort = MultiplayerManager.findAvailablePort();
|
int localPort = MultiplayerManager.findAvailablePort();
|
||||||
MultiplayerServer server = new MultiplayerServer(1000, true);
|
MultiplayerServer server = new MultiplayerServer("SessionName", 1000, true);
|
||||||
server.startServer(localPort);
|
server.startServer(localPort);
|
||||||
|
|
||||||
MultiplayerClient client = new MultiplayerClient("username", localPort);
|
MultiplayerClient client = new MultiplayerClient("username", localPort);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user