Skip to content

Commit e3dae91

Browse files
author
Rafael Oliveira
committed
feat(net): Host/Join landing page for online lobby (PR Card-Forge#9899 adapted)
- Replace single "Connect to Server" button with landing page showing Host a Game / Join a Game buttons, a warning and a wiki guide link - Split connectToServer() into hostGame() + joinGame() in controller - joinGame() opens our existing favorites/history server picker - hostGame() goes directly to server start (no dialog), then shows the address table so the host can share the URL - Add ensurePlayerName() and getJoinServerUrl() to NetConnectUtil - Remove "Host" button from join dialog (host is now a separate flow) - Add 6 new i18n strings (lblHostGame, lblJoinGame, etc.)
1 parent 57ea553 commit e3dae91

5 files changed

Lines changed: 126 additions & 46 deletions

File tree

forge-gui-desktop/src/main/java/forge/screens/home/online/CSubmenuOnlineLobby.java

Lines changed: 21 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,9 @@
3131
import forge.gui.util.SOptionPane;
3232
import forge.localinstance.properties.ForgeConstants;
3333
import forge.localinstance.properties.ForgeNetPreferences;
34-
import forge.localinstance.properties.ForgePreferences.FPref;
3534
import forge.menus.IMenuProvider;
3635
import forge.menus.MenuUtil;
3736
import forge.model.FModel;
38-
import forge.player.GamePlayerUtil;
3937
import forge.screens.home.CHomeUI;
4038
import forge.screens.home.CLobby;
4139
import forge.screens.home.VLobby;
@@ -49,7 +47,6 @@
4947

5048
import com.google.common.collect.ImmutableList;
5149
import net.miginfocom.swing.MigLayout;
52-
import org.apache.commons.lang3.StringUtils;
5350

5451
public enum CSubmenuOnlineLobby implements ICDoc, IMenuProvider {
5552
SINGLETON_INSTANCE;
@@ -61,49 +58,43 @@ void setLobby(final VLobby lobbyView) {
6158
initialize();
6259
}
6360

64-
void connectToServer() {
65-
final String url = showConnectDialog();
66-
if (url == null) { return; }
67-
61+
void hostGame() {
62+
NetConnectUtil.ensurePlayerName();
6863
FThreads.invokeInBackgroundThread(() -> {
69-
if (!url.isEmpty()) {
70-
join(url);
71-
}
72-
else {
73-
try {
74-
host();
75-
} catch (Exception ex) {
76-
// IntelliJ swears that BindException isn't thrown in this try block, but it is!
77-
if (ex.getClass() == BindException.class) {
78-
SOptionPane.showErrorDialog(Localizer.getInstance().getMessage("lblUnableStartServerPortAlreadyUse"));
79-
SOverlayUtils.hideOverlay();
80-
} else {
81-
BugReporter.reportException(ex);
82-
}
64+
try {
65+
host();
66+
} catch (Exception ex) {
67+
if (ex.getClass() == BindException.class) {
68+
SOptionPane.showErrorDialog(Localizer.getInstance().getMessage("lblUnableStartServerPortAlreadyUse"));
69+
SOverlayUtils.hideOverlay();
70+
} else {
71+
BugReporter.reportException(ex);
8372
}
8473
}
8574
});
8675
}
8776

77+
void joinGame() {
78+
final String url = showConnectDialog();
79+
if (url == null) { return; }
80+
FThreads.invokeInBackgroundThread(() -> join(url));
81+
}
82+
8883
private static String showConnectDialog() {
89-
if (StringUtils.isBlank(FModel.getPreferences().getPref(FPref.PLAYER_NAME))) {
90-
GamePlayerUtil.setPlayerName();
91-
}
84+
NetConnectUtil.ensurePlayerName();
9285

9386
final Callable<String> task = () -> {
9487
final String[] resultUrl = {null};
9588
final boolean[] accepted = {false};
9689
final FOptionPane[] paneHolder = {null};
9790

98-
// Top row: IP field + Connect + Host buttons
99-
final JPanel topRow = new JPanel(new MigLayout("insets 0, gap 4", "[grow][pref][pref]"));
91+
// Top row: IP field + Connect button
92+
final JPanel topRow = new JPanel(new MigLayout("insets 0, gap 4", "[grow][pref]"));
10093
topRow.setOpaque(false);
10194
final FTextField txtIP = new FTextField.Builder().build();
10295
final FButton btnConnect = new FButton("Connect");
103-
final FButton btnHost = new FButton("Host");
10496
topRow.add(txtIP, "growx");
10597
topRow.add(btnConnect, "w 100!, h 26!");
106-
topRow.add(btnHost, "w 100!, h 26!");
10798

10899
// Server list panel (rebuilt on star toggles)
109100
final JPanel serversPanel = new JPanel(new MigLayout("insets 0, gap 2 2, wrap 1", "[grow]"));
@@ -162,19 +153,15 @@ private static String showConnectDialog() {
162153
if (paneHolder[0] != null) { paneHolder[0].setResult(0); }
163154
};
164155
btnConnect.addActionListener(e -> doConnect.run());
165-
btnHost.addActionListener(e -> {
166-
resultUrl[0] = "";
167-
accepted[0] = true;
168-
if (paneHolder[0] != null) { paneHolder[0].setResult(0); }
169-
});
170156
txtIP.addKeyListener(new KeyAdapter() {
171157
@Override
172158
public void keyPressed(final KeyEvent e) {
173159
if (e.getKeyCode() == KeyEvent.VK_ENTER) { doConnect.run(); }
174160
}
175161
});
176162

177-
final FOptionPane pane = new FOptionPane(null, "Connect to Server", null, outer,
163+
final FOptionPane pane = new FOptionPane(null,
164+
Localizer.getInstance().getMessage("lblJoinGame"), null, outer,
178165
ImmutableList.of("Cancel"), -1);
179166
paneHolder[0] = pane;
180167
pane.setDefaultFocus(txtIP);

forge-gui-desktop/src/main/java/forge/screens/home/online/OnlineMenu.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ public final class OnlineMenu {
1818
public static JMenu getMenu() {
1919
JMenu menu = new JMenu(Localizer.getInstance().getMessage("lblOnline"));
2020
menu.setMnemonic(KeyEvent.VK_O);
21-
menu.add(getMenuItem_ConnectToServer());
21+
menu.add(getMenuItem_HostGame());
22+
menu.add(getMenuItem_JoinGame());
2223
menu.add(new JSeparator());
2324
menu.add(chatItem);
2425
return menu;
@@ -38,9 +39,15 @@ public static JMenu getMenu() {
3839
});
3940
}
4041

41-
private static JMenuItem getMenuItem_ConnectToServer() {
42-
JMenuItem menuItem = new JMenuItem(Localizer.getInstance().getMessage("lblConnectToServer"));
43-
menuItem.addActionListener(e -> CSubmenuOnlineLobby.SINGLETON_INSTANCE.connectToServer());
42+
private static JMenuItem getMenuItem_HostGame() {
43+
JMenuItem menuItem = new JMenuItem(Localizer.getInstance().getMessage("lblHostGame"));
44+
menuItem.addActionListener(e -> CSubmenuOnlineLobby.SINGLETON_INSTANCE.hostGame());
45+
return menuItem;
46+
}
47+
48+
private static JMenuItem getMenuItem_JoinGame() {
49+
JMenuItem menuItem = new JMenuItem(Localizer.getInstance().getMessage("lblJoinGame"));
50+
menuItem.addActionListener(e -> CSubmenuOnlineLobby.SINGLETON_INSTANCE.joinGame());
4451
return menuItem;
4552
}
4653
}

forge-gui-desktop/src/main/java/forge/screens/home/online/VSubmenuOnlineLobby.java

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
package forge.screens.home.online;
22

3+
import java.awt.BorderLayout;
4+
import java.awt.Color;
5+
import java.awt.Font;
6+
7+
import javax.swing.BorderFactory;
38
import javax.swing.JPanel;
9+
import javax.swing.SwingConstants;
410

511
import forge.deckchooser.FDeckChooser;
612
import forge.gamemodes.match.GameLobby;
@@ -21,6 +27,7 @@
2127
import forge.screens.home.VHomeUI;
2228
import forge.screens.home.VLobby;
2329
import forge.toolbox.FButton;
30+
import forge.toolbox.FLabel;
2431
import forge.toolbox.FSkin;
2532
import forge.util.Localizer;
2633
import net.miginfocom.swing.MigLayout;
@@ -71,11 +78,69 @@ public void populate() {
7178
container.removeAll();
7279

7380
if (lobby == null) {
74-
final FButton btnConnect = new FButton(Localizer.getInstance().getMessage("lblConnectToServer"));
75-
btnConnect.setFont(FSkin.getRelativeFont(20));
76-
btnConnect.addActionListener(e -> getLayoutControl().connectToServer());
77-
container.setLayout(new MigLayout("insets 0, gap 0, ax center, ay center"));
78-
container.add(btnConnect, "w 300!, h 75!");
81+
final Localizer localizer = Localizer.getInstance();
82+
final String guideUrl = "https://github.com/Card-Forge/forge/wiki/network-play";
83+
84+
// Bordered info box
85+
final JPanel infoBox = new JPanel(new MigLayout("insets 30 40 20 40, gap 0, wrap 1, ax center"));
86+
infoBox.setBackground(new Color(40, 40, 40));
87+
infoBox.setBorder(BorderFactory.createCompoundBorder(
88+
BorderFactory.createLineBorder(new Color(100, 100, 100), 1),
89+
BorderFactory.createEmptyBorder(10, 10, 10, 10)));
90+
91+
// Title
92+
final FLabel lblTitle = new FLabel.Builder()
93+
.text("- = * H E R E B E E L D R A Z I * = -")
94+
.fontSize(22).fontAlign(SwingConstants.CENTER).build();
95+
96+
// Warning
97+
final FLabel lblWarning = new FLabel.Builder()
98+
.text(localizer.getMessage("lblOnlineWarning"))
99+
.fontSize(16).fontAlign(SwingConstants.CENTER).build();
100+
101+
// Guide text + clickable link
102+
final FLabel lblGuideText = new FLabel.Builder()
103+
.text(localizer.getMessage("lblOnlineGuideText"))
104+
.fontSize(16).fontAlign(SwingConstants.CENTER).build();
105+
106+
final FLabel lblGuideLink = new FLabel.Builder()
107+
.text("<html><u>" + localizer.getMessage("lblNetworkPlayGuide") + "</u></html>")
108+
.fontSize(16).fontStyle(Font.BOLD).fontAlign(SwingConstants.CENTER)
109+
.hoverable().cmdClick(() -> {
110+
try {
111+
java.awt.Desktop.getDesktop().browse(java.net.URI.create(guideUrl));
112+
} catch (final Exception ex) {
113+
java.awt.Toolkit.getDefaultToolkit().getSystemClipboard()
114+
.setContents(new java.awt.datatransfer.StringSelection(guideUrl), null);
115+
}
116+
}).build();
117+
lblGuideLink.setCursor(java.awt.Cursor.getPredefinedCursor(java.awt.Cursor.HAND_CURSOR));
118+
119+
// Buttons
120+
final FButton btnHost = new FButton(localizer.getMessage("lblHostGame"));
121+
btnHost.setFont(FSkin.getRelativeFont(18));
122+
btnHost.addActionListener(e -> getLayoutControl().hostGame());
123+
124+
final FButton btnJoin = new FButton(localizer.getMessage("lblJoinGame"));
125+
btnJoin.setFont(FSkin.getRelativeFont(18));
126+
btnJoin.addActionListener(e -> getLayoutControl().joinGame());
127+
128+
final JPanel buttonPanel = new JPanel(new MigLayout("insets 0, gap 20, ax center"));
129+
buttonPanel.setOpaque(false);
130+
buttonPanel.add(btnHost, "w 200!, h 50!");
131+
buttonPanel.add(btnJoin, "w 200!, h 50!");
132+
133+
infoBox.add(lblTitle, "ax center, gap 0 0 0 15");
134+
infoBox.add(lblWarning, "ax center, gap 0 0 0 15");
135+
infoBox.add(lblGuideText, "ax center, gap 0 0 0 0");
136+
infoBox.add(lblGuideLink, "ax center, gap 0 0 0 25");
137+
infoBox.add(buttonPanel, "ax center");
138+
139+
container.setLayout(new BorderLayout());
140+
final JPanel wrapper = new JPanel(new MigLayout("ax center, ay center"));
141+
wrapper.setOpaque(false);
142+
wrapper.add(infoBox);
143+
container.add(wrapper, BorderLayout.CENTER);
79144

80145
if (container.isShowing()) {
81146
container.validate();

forge-gui/res/languages/en-US.properties

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3186,6 +3186,12 @@ lblStartingServer=Starting server...
31863186
lblConnectingToServer=Connecting to server...
31873187
#NetConnectUtil.java
31883188
lblOnlineMultiplayerDest=This feature is under active development.\nYou are likely to find bugs.\n\n - = * H E R E B E E L D R A Z I * = -\n\nEnter the URL of the server to join.\nLeave blank to host your own server.
3189+
lblHostGame=Host a Game
3190+
lblJoinGame=Join a Game
3191+
lblEnterServerAddress=Enter the server IP address and port (e.g. 192.168.1.50:36743)
3192+
lblOnlineWarning=Online multiplayer is under active development. You are likely to find bugs.
3193+
lblOnlineGuideText=For setup instructions and troubleshooting, see the
3194+
lblNetworkPlayGuide=Network Play User Guide
31893195
lblHostingPortOnN=Hosting on port {0}.
31903196
lblShareURLToMakePlayerJoinServer=Share the following URL with anyone who wishes to join your server. It has been copied to your clipboard for convenience.\n\n{0}\n\nFor internal games, use the following URL: {1}
31913197
lblForgeUnableDetermineYourExternalIP=Forge was unable to determine your external IP!\n\n{0}

forge-gui/src/main/java/forge/gamemodes/net/NetConnectUtil.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,29 @@ private static String joinUrls(final List<String> urls) {
8787
return String.join(URL_SEPARATOR, urls);
8888
}
8989

90+
/** Ensure the player name is set before connecting. */
91+
public static void ensurePlayerName() {
92+
if (StringUtils.isBlank(FModel.getPreferences().getPref(FPref.PLAYER_NAME))) {
93+
GamePlayerUtil.setPlayerName();
94+
}
95+
}
96+
97+
/** Prompt for the server address to join (used by mobile path). Returns null if cancelled. */
98+
public static String getJoinServerUrl() {
99+
final String url = SOptionPane.showInputDialog(
100+
Localizer.getInstance().getMessage("lblEnterServerAddress"),
101+
Localizer.getInstance().getMessage("lblJoinGame"));
102+
if (url == null || url.isEmpty()) { return null; }
103+
ensurePlayerName();
104+
return url;
105+
}
106+
90107
public static String getServerUrl() {
91108
final String url = SOptionPane.showInputDialog(Localizer.getInstance().getMessage("lblOnlineMultiplayerDest"), Localizer.getInstance().getMessage("lblConnectToServer"));
92109
if (url == null) { return null; }
93110

94111
//prompt user for player one name if needed
95-
if (StringUtils.isBlank(FModel.getPreferences().getPref(FPref.PLAYER_NAME))) {
96-
GamePlayerUtil.setPlayerName();
97-
}
112+
ensurePlayerName();
98113
return url;
99114
}
100115

0 commit comments

Comments
 (0)