feat(multiplayer): kick player.
This commit is contained in:
@@ -70,7 +70,8 @@ public final class MultiplayerChannel {
|
|||||||
property = "type",
|
property = "type",
|
||||||
subtypes = {
|
subtypes = {
|
||||||
@JsonSubtype(clazz = JoinResponse.class, name = "join"),
|
@JsonSubtype(clazz = JoinResponse.class, name = "join"),
|
||||||
@JsonSubtype(clazz = KeepAliveResponse.class, name = "keepalive")
|
@JsonSubtype(clazz = KeepAliveResponse.class, name = "keepalive"),
|
||||||
|
@JsonSubtype(clazz = KickResponse.class, name = "kick")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
public static class Response {
|
public static class Response {
|
||||||
@@ -101,6 +102,18 @@ public final class MultiplayerChannel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class KickResponse extends Response {
|
||||||
|
private final String msg;
|
||||||
|
|
||||||
|
public KickResponse(String msg) {
|
||||||
|
this.msg = msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMsg() {
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class CatoClient extends Event {
|
public static class CatoClient extends Event {
|
||||||
private final String username;
|
private final String username;
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import java.io.*;
|
|||||||
import java.net.ConnectException;
|
import java.net.ConnectException;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.ui.multiplayer.MultiplayerChannel.*;
|
import static org.jackhuang.hmcl.ui.multiplayer.MultiplayerChannel.*;
|
||||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||||
@@ -38,6 +39,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<>();
|
||||||
|
|
||||||
public MultiplayerClient(String id, int port) {
|
public MultiplayerClient(String id, int port) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
@@ -63,6 +65,10 @@ public class MultiplayerClient extends Thread {
|
|||||||
return onDisconnected;
|
return onDisconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public EventManager<Event> onKicked() {
|
||||||
|
return onDisconnected;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
LOG.info("Connecting to 127.0.0.1:" + port);
|
LOG.info("Connecting to 127.0.0.1:" + port);
|
||||||
@@ -72,34 +78,37 @@ public class MultiplayerClient extends Thread {
|
|||||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))) {
|
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))) {
|
||||||
LOG.info("Connected to 127.0.0.1:" + port);
|
LOG.info("Connected to 127.0.0.1:" + port);
|
||||||
|
|
||||||
|
socket.setKeepAlive(true);
|
||||||
|
|
||||||
writer.write(JsonUtils.UGLY_GSON.toJson(new JoinRequest(MultiplayerManager.CATO_VERSION, id)));
|
writer.write(JsonUtils.UGLY_GSON.toJson(new JoinRequest(MultiplayerManager.CATO_VERSION, id)));
|
||||||
writer.newLine();
|
writer.newLine();
|
||||||
writer.flush();
|
writer.flush();
|
||||||
|
|
||||||
LOG.fine("Sent join request with id=" + id);
|
LOG.fine("Sent join request with id=" + id);
|
||||||
|
|
||||||
String line = reader.readLine();
|
String line;
|
||||||
if (line == null) {
|
while ((line = reader.readLine()) != null) {
|
||||||
return;
|
if (isInterrupted()) {
|
||||||
}
|
|
||||||
|
|
||||||
JoinResponse response = JsonUtils.fromNonNullJson(line, JoinResponse.class);
|
|
||||||
setGamePort(response.getPort());
|
|
||||||
onConnected.fireEvent(new ConnectedEvent(this, response.getPort()));
|
|
||||||
|
|
||||||
LOG.fine("Received join response with port " + response.getPort());
|
|
||||||
|
|
||||||
while (!isInterrupted()) {
|
|
||||||
writer.write(verifyJson(JsonUtils.UGLY_GSON.toJson(new KeepAliveResponse(System.currentTimeMillis()))));
|
|
||||||
writer.newLine();
|
|
||||||
writer.flush();
|
|
||||||
|
|
||||||
try {
|
|
||||||
Thread.sleep(1500);
|
|
||||||
} catch (InterruptedException ignored) {
|
|
||||||
LOG.warning("MultiplayerClient interrupted");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LOG.fine("Message from server:" + line);
|
||||||
|
|
||||||
|
Response response = JsonUtils.fromNonNullJson(line, Response.class);
|
||||||
|
|
||||||
|
if (response instanceof JoinResponse) {
|
||||||
|
JoinResponse joinResponse = JsonUtils.fromNonNullJson(line, JoinResponse.class);
|
||||||
|
setGamePort(joinResponse.getPort());
|
||||||
|
onConnected.fireEvent(new ConnectedEvent(this, joinResponse.getPort()));
|
||||||
|
|
||||||
|
LOG.fine("Received join response with port " + joinResponse.getPort());
|
||||||
|
} else if (response instanceof KickResponse) {
|
||||||
|
onKicked.fireEvent(new Event(this));
|
||||||
|
|
||||||
|
LOG.fine("Kicked by the server");
|
||||||
|
} else {
|
||||||
|
LOG.log(Level.WARNING, "Unrecognized packet from server:" + line);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (ConnectException e) {
|
} catch (ConnectException e) {
|
||||||
LOG.info("Failed to connect to 127.0.0.1:" + port + ", tried " + i + " time(s)");
|
LOG.info("Failed to connect to 127.0.0.1:" + port + ", tried " + i + " time(s)");
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ import java.util.Arrays;
|
|||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.CancellationException;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
@@ -146,6 +147,9 @@ public final class MultiplayerManager {
|
|||||||
future.completeExceptionally(e);
|
future.completeExceptionally(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
client.onKicked().register(kickedEvent -> {
|
||||||
|
future.completeExceptionally(new CancellationException());
|
||||||
|
});
|
||||||
client.start();
|
client.start();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import javafx.collections.ObservableList;
|
|||||||
import javafx.scene.control.Control;
|
import javafx.scene.control.Control;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.Skin;
|
import javafx.scene.control.Skin;
|
||||||
import org.jackhuang.hmcl.Metadata;
|
|
||||||
import org.jackhuang.hmcl.event.Event;
|
import org.jackhuang.hmcl.event.Event;
|
||||||
import org.jackhuang.hmcl.setting.DownloadProviders;
|
import org.jackhuang.hmcl.setting.DownloadProviders;
|
||||||
import org.jackhuang.hmcl.task.Schedulers;
|
import org.jackhuang.hmcl.task.Schedulers;
|
||||||
@@ -41,11 +40,11 @@ import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
|||||||
import org.jackhuang.hmcl.util.StringUtils;
|
import org.jackhuang.hmcl.util.StringUtils;
|
||||||
|
|
||||||
import java.util.concurrent.CancellationException;
|
import java.util.concurrent.CancellationException;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||||
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.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;
|
||||||
@@ -211,6 +210,9 @@ public class MultiplayerPage extends Control implements DecoratorPage, PageAware
|
|||||||
int gamePort = result.getAd();
|
int gamePort = result.getAd();
|
||||||
try {
|
try {
|
||||||
MultiplayerManager.CatoSession session = MultiplayerManager.createSession(config().getMultiplayerToken(), result.getMotd(), gamePort);
|
MultiplayerManager.CatoSession session = MultiplayerManager.createSession(config().getMultiplayerToken(), result.getMotd(), gamePort);
|
||||||
|
session.getServer().onClientAdding().register(event -> {
|
||||||
|
|
||||||
|
});
|
||||||
session.getServer().onClientAdded().register(event -> {
|
session.getServer().onClientAdded().register(event -> {
|
||||||
runInFX(() -> {
|
runInFX(() -> {
|
||||||
clients.add(event);
|
clients.add(event);
|
||||||
@@ -263,19 +265,37 @@ public class MultiplayerPage extends Control implements DecoratorPage, PageAware
|
|||||||
.thenAcceptAsync(session -> {
|
.thenAcceptAsync(session -> {
|
||||||
initCatoSession(session);
|
initCatoSession(session);
|
||||||
|
|
||||||
|
AtomicBoolean kicked = new AtomicBoolean();
|
||||||
|
|
||||||
session.getClient().onDisconnected().register(() -> {
|
session.getClient().onDisconnected().register(() -> {
|
||||||
runInFX(() -> {
|
runInFX(() -> {
|
||||||
stopCatoSession();
|
stopCatoSession();
|
||||||
Controllers.dialog(i18n("multiplayer.session.join.lost_connection"));
|
if (!kicked.get()) {
|
||||||
|
Controllers.dialog(i18n("multiplayer.session.join.lost_connection"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
session.getClient().onKicked().register(() -> {
|
||||||
|
runInFX(() -> {
|
||||||
|
kicked.set(true);
|
||||||
|
Controllers.dialog(i18n("multiplayer.session.join.kicked"));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
gamePort.set(session.getClient().getGamePort());
|
gamePort.set(session.getClient().getGamePort());
|
||||||
setMultiplayerState(MultiplayerManager.State.SLAVE);
|
setMultiplayerState(MultiplayerManager.State.SLAVE);
|
||||||
resolve.run();
|
resolve.run();
|
||||||
}, Platform::runLater).exceptionally(throwable -> {
|
}, Platform::runLater)
|
||||||
LOG.log(Level.WARNING, "Failed to join sessoin");
|
.exceptionally(throwable -> {
|
||||||
reject.accept(i18n("multiplayer.session.error"));
|
if (throwable instanceof CancellationException) {
|
||||||
|
LOG.info("Connection rejected by the server");
|
||||||
|
reject.accept(i18n("multiplayer.session.join.rejected"));
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
LOG.log(Level.WARNING, "Failed to join sessoin");
|
||||||
|
reject.accept(i18n("multiplayer.session.error"));
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
} catch (MultiplayerManager.IncompatibleCatoVersionException e) {
|
} catch (MultiplayerManager.IncompatibleCatoVersionException e) {
|
||||||
@@ -286,6 +306,17 @@ public class MultiplayerPage extends Control implements DecoratorPage, PageAware
|
|||||||
.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())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void kickPlayer(MultiplayerChannel.CatoClient client) {
|
||||||
|
if (getSession() == null || !getSession().isReady() || getMultiplayerState() != MultiplayerManager.State.MASTER) {
|
||||||
|
throw new IllegalStateException("CatoSession not ready");
|
||||||
|
}
|
||||||
|
|
||||||
|
Controllers.confirm(i18n("multiplayer.session.create.members.kick.prompt"), i18n("multiplayer.session.create.members.kick"), MessageDialogPane.MessageType.WARNING,
|
||||||
|
() -> {
|
||||||
|
getSession().getServer().kickPlayer(client);
|
||||||
|
}, null);
|
||||||
|
}
|
||||||
|
|
||||||
public void closeRoom() {
|
public void closeRoom() {
|
||||||
if (getSession() == null || !getSession().isReady() || getMultiplayerState() != MultiplayerManager.State.MASTER) {
|
if (getSession() == null || !getSession().isReady() || getMultiplayerState() != MultiplayerManager.State.MASTER) {
|
||||||
throw new IllegalStateException("CatoSession not ready");
|
throw new IllegalStateException("CatoSession not ready");
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import org.jackhuang.hmcl.Metadata;
|
|||||||
import org.jackhuang.hmcl.game.LauncherHelper;
|
import org.jackhuang.hmcl.game.LauncherHelper;
|
||||||
import org.jackhuang.hmcl.setting.Profile;
|
import org.jackhuang.hmcl.setting.Profile;
|
||||||
import org.jackhuang.hmcl.setting.Profiles;
|
import org.jackhuang.hmcl.setting.Profiles;
|
||||||
|
import org.jackhuang.hmcl.setting.Theme;
|
||||||
import org.jackhuang.hmcl.ui.Controllers;
|
import org.jackhuang.hmcl.ui.Controllers;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
import org.jackhuang.hmcl.ui.SVG;
|
import org.jackhuang.hmcl.ui.SVG;
|
||||||
@@ -315,12 +316,18 @@ public class MultiplayerPageSkin extends SkinBase<MultiplayerPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class ClientItem extends StackPane {
|
private class ClientItem extends StackPane {
|
||||||
ClientItem(MultiplayerChannel.CatoClient client) {
|
ClientItem(MultiplayerChannel.CatoClient client) {
|
||||||
BorderPane pane = new BorderPane();
|
BorderPane pane = new BorderPane();
|
||||||
pane.setPadding(new Insets(8));
|
pane.setPadding(new Insets(8));
|
||||||
pane.setLeft(new Label(client.getUsername()));
|
pane.setLeft(new Label(client.getUsername()));
|
||||||
|
|
||||||
|
JFXButton kickButton = new JFXButton();
|
||||||
|
kickButton.setGraphic(SVG.close(Theme.blackFillBinding(), 16, 16));
|
||||||
|
kickButton.getStyleClass().add("toggle-icon-tiny");
|
||||||
|
kickButton.setOnAction(e -> getSkinnable().kickPlayer(client));
|
||||||
|
pane.setRight(kickButton);
|
||||||
|
|
||||||
RipplerContainer container = new RipplerContainer(pane);
|
RipplerContainer container = new RipplerContainer(pane);
|
||||||
getChildren().setAll(container);
|
getChildren().setAll(container);
|
||||||
getStyleClass().add("md-list-cell");
|
getStyleClass().add("md-list-cell");
|
||||||
|
|||||||
@@ -20,12 +20,17 @@ package org.jackhuang.hmcl.ui.multiplayer;
|
|||||||
import com.google.gson.JsonParseException;
|
import com.google.gson.JsonParseException;
|
||||||
import org.jackhuang.hmcl.event.Event;
|
import org.jackhuang.hmcl.event.Event;
|
||||||
import org.jackhuang.hmcl.event.EventManager;
|
import org.jackhuang.hmcl.event.EventManager;
|
||||||
|
import org.jackhuang.hmcl.util.FutureCallback;
|
||||||
import org.jackhuang.hmcl.util.Lang;
|
import org.jackhuang.hmcl.util.Lang;
|
||||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.ui.multiplayer.MultiplayerChannel.*;
|
import static org.jackhuang.hmcl.ui.multiplayer.MultiplayerChannel.*;
|
||||||
@@ -35,9 +40,12 @@ public class MultiplayerServer extends Thread {
|
|||||||
private ServerSocket socket;
|
private ServerSocket socket;
|
||||||
private final int gamePort;
|
private final int gamePort;
|
||||||
|
|
||||||
|
private FutureCallback<CatoClient> onClientAdding;
|
||||||
private final EventManager<MultiplayerChannel.CatoClient> onClientAdded = new EventManager<>();
|
private final EventManager<MultiplayerChannel.CatoClient> onClientAdded = new EventManager<>();
|
||||||
private final EventManager<MultiplayerChannel.CatoClient> onClientDisconnected = new EventManager<>();
|
private final EventManager<MultiplayerChannel.CatoClient> onClientDisconnected = new EventManager<>();
|
||||||
private final EventManager<Event> onKeepAlive = new EventManager<>();
|
|
||||||
|
private final Map<String, Client> clients = new ConcurrentHashMap<>();
|
||||||
|
private final Map<String, Client> nameClientMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public MultiplayerServer(int gamePort) {
|
public MultiplayerServer(int gamePort) {
|
||||||
this.gamePort = gamePort;
|
this.gamePort = gamePort;
|
||||||
@@ -46,6 +54,10 @@ public class MultiplayerServer extends Thread {
|
|||||||
setDaemon(true);
|
setDaemon(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setOnClientAdding(FutureCallback<CatoClient> callback) {
|
||||||
|
onClientAdding = callback;
|
||||||
|
}
|
||||||
|
|
||||||
public EventManager<MultiplayerChannel.CatoClient> onClientAdded() {
|
public EventManager<MultiplayerChannel.CatoClient> onClientAdded() {
|
||||||
return onClientAdded;
|
return onClientAdded;
|
||||||
}
|
}
|
||||||
@@ -54,10 +66,6 @@ public class MultiplayerServer extends Thread {
|
|||||||
return onClientDisconnected;
|
return onClientDisconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EventManager<Event> onKeepAlive() {
|
|
||||||
return onKeepAlive;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startServer() throws IOException {
|
public void startServer() throws IOException {
|
||||||
startServer(0);
|
startServer(0);
|
||||||
}
|
}
|
||||||
@@ -92,12 +100,31 @@ public class MultiplayerServer extends Thread {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void kickPlayer(CatoClient player) {
|
||||||
|
Client client = nameClientMap.get(player.getUsername());
|
||||||
|
if (client == null) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (client.socket.isConnected()) {
|
||||||
|
client.write(new KickResponse());
|
||||||
|
client.socket.close();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.log(Level.WARNING, "Failed to kick player " + player.getUsername() + ". Maybe already disconnected?", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void handleClient(Socket targetSocket) {
|
private void handleClient(Socket targetSocket) {
|
||||||
|
String address = targetSocket.getRemoteSocketAddress().toString();
|
||||||
String clientName = null;
|
String clientName = null;
|
||||||
LOG.info("Accepted client " + targetSocket.getRemoteSocketAddress());
|
LOG.info("Accepted client " + address);
|
||||||
try (Socket clientSocket = targetSocket;
|
try (Socket clientSocket = targetSocket;
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
|
BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
|
||||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()))) {
|
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()))) {
|
||||||
|
clientSocket.setKeepAlive(true);
|
||||||
|
Client client = new Client(clientSocket, writer);
|
||||||
|
clients.put(address, client);
|
||||||
|
|
||||||
String line;
|
String line;
|
||||||
while ((line = reader.readLine()) != null) {
|
while ((line = reader.readLine()) != null) {
|
||||||
if (isInterrupted()) {
|
if (isInterrupted()) {
|
||||||
@@ -110,19 +137,35 @@ public class MultiplayerServer extends Thread {
|
|||||||
if (request instanceof JoinRequest) {
|
if (request instanceof JoinRequest) {
|
||||||
JoinRequest joinRequest = (JoinRequest) request;
|
JoinRequest joinRequest = (JoinRequest) request;
|
||||||
LOG.info("Received join request with clientVersion=" + joinRequest.getClientVersion() + ", id=" + joinRequest.getUsername());
|
LOG.info("Received join request with clientVersion=" + joinRequest.getClientVersion() + ", id=" + joinRequest.getUsername());
|
||||||
|
|
||||||
writer.write(verifyJson(JsonUtils.UGLY_GSON.toJson(new JoinResponse(gamePort))));
|
|
||||||
writer.newLine();
|
|
||||||
writer.flush();
|
|
||||||
|
|
||||||
clientName = joinRequest.getUsername();
|
clientName = joinRequest.getUsername();
|
||||||
onClientAdded.fireEvent(new CatoClient(this, joinRequest.getUsername()));
|
|
||||||
} else if (request instanceof KeepAliveRequest) {
|
|
||||||
writer.write(JsonUtils.UGLY_GSON.toJson(new KeepAliveResponse(System.currentTimeMillis())));
|
|
||||||
writer.newLine();
|
|
||||||
writer.flush();
|
|
||||||
|
|
||||||
onKeepAlive.fireEvent(new Event(this));
|
CatoClient catoClient = new CatoClient(this, clientName);
|
||||||
|
nameClientMap.put(clientName, client);
|
||||||
|
onClientAdded.fireEvent(catoClient);
|
||||||
|
|
||||||
|
if (onClientAdding != null) {
|
||||||
|
onClientAdding.call(catoClient, () -> {
|
||||||
|
try {
|
||||||
|
client.write(new JoinResponse(gamePort));
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.log(Level.WARNING, "Failed to send join response.", e);
|
||||||
|
try {
|
||||||
|
socket.close();
|
||||||
|
} catch (IOException ioException) {
|
||||||
|
LOG.log(Level.WARNING, "Failed to close socket caused by join response sending failure.", e);
|
||||||
|
this.interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, msg -> {
|
||||||
|
try {
|
||||||
|
client.write(new KickResponse(msg));
|
||||||
|
LOG.info("Rejected join request from id=" + joinRequest.getUsername());
|
||||||
|
socket.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.log(Level.WARNING, "Failed to send kick response.", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
LOG.log(Level.WARNING, "Unrecognized packet from client " + targetSocket.getRemoteSocketAddress() + ":" + line);
|
LOG.log(Level.WARNING, "Unrecognized packet from client " + targetSocket.getRemoteSocketAddress() + ":" + line);
|
||||||
}
|
}
|
||||||
@@ -135,6 +178,25 @@ public class MultiplayerServer extends Thread {
|
|||||||
if (clientName != null) {
|
if (clientName != null) {
|
||||||
onClientDisconnected.fireEvent(new CatoClient(this, clientName));
|
onClientDisconnected.fireEvent(new CatoClient(this, clientName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clients.remove(address);
|
||||||
|
if (clientName != null) nameClientMap.remove(clientName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Client {
|
||||||
|
public final Socket socket;
|
||||||
|
public final BufferedWriter writer;
|
||||||
|
|
||||||
|
public Client(Socket socket, BufferedWriter writer) {
|
||||||
|
this.socket = socket;
|
||||||
|
this.writer = writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void write(Object object) throws IOException {
|
||||||
|
writer.write(verifyJson(JsonUtils.UGLY_GSON.toJson(object)));
|
||||||
|
writer.newLine();
|
||||||
|
writer.flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -644,8 +644,10 @@ multiplayer.session.join.hint=You must obtain the invitation code from the gamer
|
|||||||
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 room.
|
multiplayer.session.join.invitation_code.error=Incorrect invitation code. Please obtain invitation code from the player who creates the multiplayer room.
|
||||||
multiplayer.session.join.invitation_code.version=Versions of multiplayer functionalities are not the same among you.
|
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.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.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?
|
||||||
|
|||||||
@@ -643,8 +643,10 @@ 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.invitation_code.version=多人聯機功能版本號不一致,請保證連接多人聯機功能版本號一致。
|
||||||
|
multiplayer.session.join.kicked=你已經被房主踢出房間,你將與房間失去連接。
|
||||||
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.members=房間成員
|
multiplayer.session.members=房間成員
|
||||||
multiplayer.session.quit=退出房間
|
multiplayer.session.quit=退出房間
|
||||||
multiplayer.session.quit.warning=退出房間後,你將會與伺服器斷開連接,是否繼續?
|
multiplayer.session.quit.warning=退出房間後,你將會與伺服器斷開連接,是否繼續?
|
||||||
|
|||||||
@@ -643,8 +643,10 @@ 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.invitation_code.version=多人联机功能版本号不一致,请保证连接多人联机功能版本号一致。
|
||||||
|
multiplayer.session.join.kicked=你已经被房主踢出房间,你将与房间失去连接。
|
||||||
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.members=房间成员
|
multiplayer.session.members=房间成员
|
||||||
multiplayer.session.quit=退出房间
|
multiplayer.session.quit=退出房间
|
||||||
multiplayer.session.quit.warning=退出房间后,你将会与服务器断开连接,是否继续?
|
multiplayer.session.quit.warning=退出房间后,你将会与服务器断开连接,是否继续?
|
||||||
|
|||||||
Reference in New Issue
Block a user