feat(multiplayer): an option for auto allowing all join requests. Closes #1093.
This commit is contained in:
@@ -23,6 +23,8 @@ import com.jfoenix.controls.JFXTextField;
|
||||
import com.jfoenix.validation.base.ValidatorBase;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.ColumnConstraints;
|
||||
@@ -67,8 +69,8 @@ public class PromptDialogPane extends DialogPane {
|
||||
}
|
||||
bindings.add(Bindings.createBooleanBinding(textField::validate, textField.textProperty()));
|
||||
|
||||
if (StringUtils.isNotBlank(question.question)) {
|
||||
body.addRow(rowIndex++, new Label(question.question), textField);
|
||||
if (StringUtils.isNotBlank(question.question.get())) {
|
||||
body.addRow(rowIndex++, new Label(question.question.get()), textField);
|
||||
} else {
|
||||
GridPane.setColumnSpan(textField, 2);
|
||||
body.addRow(rowIndex++, textField);
|
||||
@@ -82,7 +84,7 @@ public class PromptDialogPane extends DialogPane {
|
||||
HBox.setMargin(checkBox, new Insets(0, 0, 0, -10));
|
||||
checkBox.setSelected(((Builder.BooleanQuestion) question).value);
|
||||
checkBox.selectedProperty().addListener((a, b, newValue) -> ((Builder.BooleanQuestion) question).value = newValue);
|
||||
checkBox.setText(question.question);
|
||||
checkBox.setText(question.question.get());
|
||||
body.addRow(rowIndex++, hBox);
|
||||
} else if (question instanceof Builder.CandidatesQuestion) {
|
||||
JFXComboBox<String> comboBox = new JFXComboBox<>();
|
||||
@@ -90,8 +92,8 @@ public class PromptDialogPane extends DialogPane {
|
||||
comboBox.getSelectionModel().selectedIndexProperty().addListener((a, b, newValue) ->
|
||||
((Builder.CandidatesQuestion) question).value = newValue.intValue());
|
||||
comboBox.getSelectionModel().select(0);
|
||||
if (StringUtils.isNotBlank(question.question)) {
|
||||
body.addRow(rowIndex++, new Label(question.question), comboBox);
|
||||
if (StringUtils.isNotBlank(question.question.get())) {
|
||||
body.addRow(rowIndex++, new Label(question.question.get()), comboBox);
|
||||
} else {
|
||||
GridPane.setColumnSpan(comboBox, 2);
|
||||
body.addRow(rowIndex++, comboBox);
|
||||
@@ -99,7 +101,7 @@ public class PromptDialogPane extends DialogPane {
|
||||
} else if (question instanceof Builder.HintQuestion) {
|
||||
HintPane pane = new HintPane();
|
||||
GridPane.setColumnSpan(pane, 2);
|
||||
pane.setText(question.question);
|
||||
pane.textProperty().bind(question.question);
|
||||
body.addRow(rowIndex++, pane);
|
||||
}
|
||||
}
|
||||
@@ -142,16 +144,28 @@ public class PromptDialogPane extends DialogPane {
|
||||
}
|
||||
|
||||
public static class Question<T> {
|
||||
public final String question;
|
||||
public final StringProperty question = new SimpleStringProperty();
|
||||
protected T value;
|
||||
|
||||
public Question(String question) {
|
||||
this.question = question;
|
||||
this.question.set(question);
|
||||
}
|
||||
|
||||
public T getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String getQuestion() {
|
||||
return question.get();
|
||||
}
|
||||
|
||||
public StringProperty questionProperty() {
|
||||
return question;
|
||||
}
|
||||
|
||||
public void setQuestion(String question) {
|
||||
this.question.set(question);
|
||||
}
|
||||
}
|
||||
|
||||
public static class HintQuestion extends Question<Void> {
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.multiplayer;
|
||||
|
||||
import com.jfoenix.controls.JFXCheckBox;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ProgressIndicator;
|
||||
import javafx.scene.layout.ColumnConstraints;
|
||||
@@ -35,12 +38,13 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class CreateMultiplayerRoomDialog extends DialogPane implements DialogAware {
|
||||
|
||||
private final FutureCallback<LocalServerDetector.PingResponse> callback;
|
||||
private final FutureCallback<CreationRequest> callback;
|
||||
private final LocalServerDetector lanServerDetectorThread;
|
||||
private final BooleanProperty allowAllJoinRequests = new SimpleBooleanProperty(true);
|
||||
|
||||
private LocalServerDetector.PingResponse server;
|
||||
|
||||
CreateMultiplayerRoomDialog(FutureCallback<LocalServerDetector.PingResponse> callback) {
|
||||
CreateMultiplayerRoomDialog(FutureCallback<CreationRequest> callback) {
|
||||
this.callback = callback;
|
||||
|
||||
setTitle(i18n("multiplayer.session.create"));
|
||||
@@ -70,6 +74,11 @@ public class CreateMultiplayerRoomDialog extends DialogPane implements DialogAwa
|
||||
portLabel.setText(i18n("multiplayer.nat.testing"));
|
||||
body.addRow(2, new Label(i18n("multiplayer.session.create.port")), portLabel);
|
||||
|
||||
JFXCheckBox allowAllJoinRequestsCheckBox = new JFXCheckBox(i18n("multiplayer.session.create.join.allow"));
|
||||
allowAllJoinRequestsCheckBox.selectedProperty().bindBidirectional(allowAllJoinRequests);
|
||||
GridPane.setColumnSpan(allowAllJoinRequestsCheckBox, 2);
|
||||
body.addRow(3, allowAllJoinRequestsCheckBox);
|
||||
|
||||
setValid(false);
|
||||
|
||||
JFXHyperlink noinLink = new JFXHyperlink();
|
||||
@@ -102,7 +111,10 @@ public class CreateMultiplayerRoomDialog extends DialogPane implements DialogAwa
|
||||
protected void onAccept() {
|
||||
setLoading();
|
||||
|
||||
callback.call(Objects.requireNonNull(server), () -> {
|
||||
callback.call(new CreationRequest(
|
||||
Objects.requireNonNull(server),
|
||||
allowAllJoinRequests.get()
|
||||
), () -> {
|
||||
runInFX(this::onSuccess);
|
||||
}, msg -> {
|
||||
runInFX(() -> onFailure(msg));
|
||||
@@ -121,4 +133,21 @@ public class CreateMultiplayerRoomDialog extends DialogPane implements DialogAwa
|
||||
lanServerDetectorThread.interrupt();
|
||||
}
|
||||
|
||||
public static class CreationRequest {
|
||||
private final LocalServerDetector.PingResponse server;
|
||||
private final boolean allowAllJoinRequests;
|
||||
|
||||
public CreationRequest(LocalServerDetector.PingResponse server, boolean allowAllJoinRequests) {
|
||||
this.server = server;
|
||||
this.allowAllJoinRequests = allowAllJoinRequests;
|
||||
}
|
||||
|
||||
public LocalServerDetector.PingResponse getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
public boolean isAllowAllJoinRequests() {
|
||||
return allowAllJoinRequests;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ public final class MultiplayerManager {
|
||||
return Metadata.HMCL_DIRECTORY.resolve("libraries").resolve(CATO_PATH);
|
||||
}
|
||||
|
||||
public static CompletableFuture<CatoSession> joinSession(String token, String version, String sessionName, String peer, Mode mode, int remotePort, int localPort) throws IncompatibleCatoVersionException {
|
||||
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);
|
||||
}
|
||||
@@ -133,6 +133,16 @@ public final class MultiplayerManager {
|
||||
MultiplayerClient client = new MultiplayerClient(session.getId(), localPort);
|
||||
session.addRelatedThread(client);
|
||||
session.setClient(client);
|
||||
|
||||
if (handler != null) {
|
||||
handler.onWaitingForJoinResponse();
|
||||
}
|
||||
|
||||
TimerTask task = Lang.setTimeout(() -> {
|
||||
future.completeExceptionally(new JoinRequestTimeoutException());
|
||||
session.stop();
|
||||
}, 30 * 1000);
|
||||
|
||||
client.onConnected().register(connectedEvent -> {
|
||||
try {
|
||||
int port = findAvailablePort();
|
||||
@@ -148,10 +158,12 @@ public final class MultiplayerManager {
|
||||
future.completeExceptionally(e);
|
||||
session.stop();
|
||||
}
|
||||
task.cancel();
|
||||
});
|
||||
client.onKicked().register(kickedEvent -> {
|
||||
future.completeExceptionally(new CancellationException());
|
||||
session.stop();
|
||||
task.cancel();
|
||||
});
|
||||
client.start();
|
||||
});
|
||||
@@ -160,7 +172,7 @@ public final class MultiplayerManager {
|
||||
});
|
||||
}
|
||||
|
||||
public static CatoSession createSession(String token, String sessionName, int gamePort) throws IOException {
|
||||
public static CatoSession createSession(String token, String sessionName, int gamePort, boolean allowAllJoinRequests) throws IOException {
|
||||
Path exe = getCatoExecutable();
|
||||
if (!Files.isRegularFile(exe)) {
|
||||
throw new IllegalStateException("Cato file not found");
|
||||
@@ -170,7 +182,7 @@ public final class MultiplayerManager {
|
||||
throw new CatoAlreadyStartedException();
|
||||
}
|
||||
|
||||
MultiplayerServer server = new MultiplayerServer(gamePort);
|
||||
MultiplayerServer server = new MultiplayerServer(gamePort, allowAllJoinRequests);
|
||||
server.startServer();
|
||||
|
||||
String[] commands = new String[]{exe.toString(),
|
||||
@@ -419,6 +431,10 @@ public final class MultiplayerManager {
|
||||
}
|
||||
}
|
||||
|
||||
public interface JoinSessionHandler {
|
||||
void onWaitingForJoinResponse();
|
||||
}
|
||||
|
||||
public static class IncompatibleCatoVersionException extends Exception {
|
||||
private final String expected;
|
||||
private final String actual;
|
||||
@@ -448,4 +464,7 @@ public final class MultiplayerManager {
|
||||
|
||||
public static class CatoAlreadyStartedException extends RuntimeException {
|
||||
}
|
||||
|
||||
public static class JoinRequestTimeoutException extends RuntimeException {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,9 +210,9 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP
|
||||
}
|
||||
|
||||
Controllers.dialog(new CreateMultiplayerRoomDialog((result, resolve, reject) -> {
|
||||
int gamePort = result.getAd();
|
||||
int gamePort = result.getServer().getAd();
|
||||
try {
|
||||
MultiplayerManager.CatoSession session = MultiplayerManager.createSession(globalConfig().getMultiplayerToken(), result.getMotd(), gamePort);
|
||||
MultiplayerManager.CatoSession session = MultiplayerManager.createSession(globalConfig().getMultiplayerToken(), result.getServer().getMotd(), gamePort, result.isAllowAllJoinRequests());
|
||||
session.getServer().setOnClientAdding((client, resolveClient, rejectClient) -> {
|
||||
runInFX(() -> {
|
||||
Controllers.confirm(i18n("multiplayer.session.create.join.prompt", client.getUsername()), i18n("multiplayer.session.create.join"), MessageDialogPane.MessageType.INFO,
|
||||
@@ -251,6 +251,8 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP
|
||||
}
|
||||
|
||||
Controllers.prompt(new PromptDialogPane.Builder(i18n("multiplayer.session.join"), (result, resolve, reject) -> {
|
||||
PromptDialogPane.Builder.HintQuestion hintQuestion = (PromptDialogPane.Builder.HintQuestion) result.get(0);
|
||||
|
||||
String invitationCode = ((PromptDialogPane.Builder.StringQuestion) result.get(1)).getValue();
|
||||
MultiplayerManager.Invitation invitation;
|
||||
try {
|
||||
@@ -279,7 +281,12 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP
|
||||
? MultiplayerManager.Mode.RELAY
|
||||
: MultiplayerManager.Mode.P2P,
|
||||
invitation.getChannelPort(),
|
||||
localPort)
|
||||
localPort, new MultiplayerManager.JoinSessionHandler() {
|
||||
@Override
|
||||
public void onWaitingForJoinResponse() {
|
||||
hintQuestion.setQuestion(i18n("multiplayer.session.join.wait"));
|
||||
}
|
||||
})
|
||||
.thenAcceptAsync(session -> {
|
||||
initCatoSession(session);
|
||||
|
||||
@@ -314,6 +321,10 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP
|
||||
LOG.info("Cato already started");
|
||||
reject.accept(i18n("multiplayer.session.error.already_started"));
|
||||
return null;
|
||||
} else if (throwable instanceof MultiplayerManager.JoinRequestTimeoutException) {
|
||||
LOG.info("Cato already started");
|
||||
reject.accept(i18n("multiplayer.session.join.wait_timeout"));
|
||||
return null;
|
||||
} else {
|
||||
LOG.log(Level.WARNING, "Failed to join sessoin");
|
||||
reject.accept(i18n("multiplayer.session.join.error"));
|
||||
|
||||
@@ -37,6 +37,7 @@ import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
public class MultiplayerServer extends Thread {
|
||||
private ServerSocket socket;
|
||||
private final int gamePort;
|
||||
private final boolean allowAllJoinRequests;
|
||||
|
||||
private FutureCallback<CatoClient> onClientAdding;
|
||||
private final EventManager<MultiplayerChannel.CatoClient> onClientAdded = new EventManager<>();
|
||||
@@ -46,8 +47,9 @@ 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) {
|
||||
public MultiplayerServer(int gamePort, boolean allowAllJoinRequests) {
|
||||
this.gamePort = gamePort;
|
||||
this.allowAllJoinRequests = allowAllJoinRequests;
|
||||
|
||||
setName("MultiplayerServer");
|
||||
setDaemon(true);
|
||||
@@ -146,7 +148,7 @@ public class MultiplayerServer extends Thread {
|
||||
nameClientMap.put(clientName, endpoint);
|
||||
onClientAdded.fireEvent(catoClient);
|
||||
|
||||
if (onClientAdding != null) {
|
||||
if (onClientAdding != null && !allowAllJoinRequests) {
|
||||
onClientAdding.call(catoClient, () -> {
|
||||
try {
|
||||
endpoint.write(new JoinResponse(gamePort));
|
||||
@@ -168,6 +170,9 @@ public class MultiplayerServer extends Thread {
|
||||
LOG.log(Level.WARNING, "Failed to send kick response.", e);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Allow all join requests.
|
||||
endpoint.write(new JoinResponse(gamePort));
|
||||
}
|
||||
} else if (request instanceof KeepAliveRequest) {
|
||||
endpoint.write(new KeepAliveResponse(System.currentTimeMillis()));
|
||||
|
||||
@@ -652,6 +652,7 @@ multiplayer.session.create=Create Session
|
||||
multiplayer.session.create.error=Failed to create multiplayer session.
|
||||
multiplayer.session.create.hint=Before creating multiplayer session, you must click "Open LAN Server" in running game, and type the port displayed in game in the blank below.
|
||||
multiplayer.session.create.join=Connection request
|
||||
multiplayer.session.create.join.allow=Accepts all join requests (When disabled, you need to accept join requests manually, so you can prevent from attacks.)
|
||||
multiplayer.session.create.join.prompt=Player %s wants to join the multiplayer session. Accept?
|
||||
multiplayer.session.create.members=Members
|
||||
multiplayer.session.create.members.kick=Kick
|
||||
@@ -675,6 +676,8 @@ multiplayer.session.join.kicked=You have been kicked by the session holder. 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?
|
||||
|
||||
@@ -652,6 +652,7 @@ multiplayer.session.create=創建房間
|
||||
multiplayer.session.create.error=創建聯機房間失敗。
|
||||
multiplayer.session.create.hint=創建聯機房間前,你需要先在正在運行的遊戲內的遊戲菜單中選擇 對區域網路開放 選項,然後在下方的輸入框中輸入遊戲內提示的埠號(通常是 5 位的數字)
|
||||
multiplayer.session.create.join=連接申請
|
||||
multiplayer.session.create.join.allow=自動接受所有連接申請(不啟用此選項時,你需要手動同意申請,以避免不相關人士誤連你的伺服器)
|
||||
multiplayer.session.create.join.prompt=玩家 %s 申請加入多人聯機房間,是否接受?
|
||||
multiplayer.session.create.members=成員
|
||||
multiplayer.session.create.members.kick=踢出房間
|
||||
@@ -674,6 +675,8 @@ multiplayer.session.join.kicked=你已經被房主踢出房間,你將與房間
|
||||
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=退出房間後,你將會與伺服器斷開連接,是否繼續?
|
||||
|
||||
@@ -652,6 +652,7 @@ multiplayer.session.create=创建房间
|
||||
multiplayer.session.create.error=创建联机房间失败。
|
||||
multiplayer.session.create.hint=创建联机房间前,你需要先在正在运行的游戏内的游戏菜单中选择 对局域网开放 选项,然后在下方的输入框中确认游戏内提示的端口号(通常是 5 位的数字)
|
||||
multiplayer.session.create.join=连接申请
|
||||
multiplayer.session.create.join.allow=自动接受所有连接申请(不启用此选项时,你需要手动同意申请,以避免不相关人士误连你的服务器)
|
||||
multiplayer.session.create.join.prompt=玩家 %s 申请加入多人联机房间,是否接受?
|
||||
multiplayer.session.create.members=成员
|
||||
multiplayer.session.create.members.kick=踢出房间
|
||||
@@ -674,6 +675,8 @@ multiplayer.session.join.kicked=你已经被房主踢出房间,你将与房间
|
||||
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=退出房间后,你将会与服务器断开连接,是否继续?
|
||||
|
||||
Reference in New Issue
Block a user