Skip to content

Commit d9655c9

Browse files
Network multiplayer optimization (UI improvements) (#9671)
- Connection errors display detailed messages instead of generic failure - Waiting timer shows player name and elapsed time - Add volatile to timer fields for thread safety - 1-second reschedule loop for network games only * Add lobby chat enhancements extracted from NetworkPlay/main These server-side chat features were missed during the earlier extraction to NetworkPlay/chat (since merged to master). Moving here as UI-focused. - Broadcast ready state with player count when toggling ready - Show (Host) indicator next to host's chat messages - Distinguish host login ('Lobby hosted by') vs player join ('joined the lobby') - Detect and announce player name changes - Fix duplicate message display by removing MessageEvent from LobbyInputHandler - Use 'left the lobby' instead of 'left the room' for consistency Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Refactor GuiBase.isNetworkplay to support per-instance query (PR 9671 feedback) Adds an overloaded isNetworkplay(IGuiGame) that checks whether a specific game instance is networked, rather than relying solely on the global static boolean which reflects whichever lobby last called setNetworkplay(). Unmigrated call sites (no-arg fallback, no IGuiGame in scope): - FThreads (utility class, no game context) Co-authored-by: tool4EvEr <tool4EvEr@>
1 parent 9274f70 commit d9655c9

File tree

27 files changed

+298
-46
lines changed

27 files changed

+298
-46
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,6 @@ forge-gui/tools/PerSetTrackingResults
9494
# Ignore python temporaries
9595
__pycache__
9696
*.pyc
97+
98+
# Ignore Claude configuration
99+
.claude

forge-gui-desktop/src/main/java/forge/GuiDesktop.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,14 @@ private static float initializeScreenScale() {
368368
}
369369
static float screenScale = initializeScreenScale();
370370

371+
@Override
372+
public boolean hasNetGame() {
373+
if (Singletons.getView() == null || Singletons.getView().getNavigationBar() == null) {
374+
return false;
375+
}
376+
return Singletons.getView().getNavigationBar().hasNetGame();
377+
}
378+
371379
@Override
372380
public float getScreenScale() {
373381
return screenScale;

forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
import forge.gamemodes.match.LobbySlotType;
3434
import forge.gamemodes.net.event.UpdateLobbyPlayerEvent;
3535
import forge.gui.CardDetailPanel;
36-
import forge.gui.GuiBase;
3736
import forge.gui.SwingPrefBinders;
3837
import forge.gui.interfaces.ILobbyView;
3938
import forge.gui.util.SOptionPane;
@@ -247,8 +246,6 @@ public void update(final boolean fullUpdate) {
247246

248247
final boolean allowNetworking = lobby.isAllowNetworking();
249248

250-
GuiBase.setNetworkplay(allowNetworking);
251-
252249
ImmutableList<VariantCheckBox> vntBoxes = null;
253250
if (allowNetworking) {
254251
vntBoxes = vntBoxesNetwork;

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,16 @@ private void join(final String url) {
8686
});
8787

8888
final ChatMessage result = NetConnectUtil.join(url, VSubmenuOnlineLobby.SINGLETON_INSTANCE, FNetOverlay.SINGLETON_INSTANCE);
89-
if(Objects.equals(result.getMessage(), ForgeConstants.CLOSE_CONN_COMMAND)) {
89+
String message = result.getMessage();
90+
if(Objects.equals(message, ForgeConstants.CLOSE_CONN_COMMAND)) {
9091
FOptionPane.showErrorDialog(Localizer.getInstance().getMessage("UnableConnectToServer", url));
9192
SOverlayUtils.hideOverlay();
92-
} else if (Objects.equals(result.getMessage(), ForgeConstants.INVALID_HOST_COMMAND)) {
93+
} else if (message != null && message.startsWith(ForgeConstants.CONN_ERROR_PREFIX)) {
94+
// Show detailed connection error
95+
String errorDetail = message.substring(ForgeConstants.CONN_ERROR_PREFIX.length());
96+
FOptionPane.showErrorDialog(errorDetail, Localizer.getInstance().getMessage("lblConnectionError"));
97+
SOverlayUtils.hideOverlay();
98+
} else if (Objects.equals(message, ForgeConstants.INVALID_HOST_COMMAND)) {
9399
FOptionPane.showErrorDialog(Localizer.getInstance().getMessage("lblDetectedInvalidHostAddress", url));
94100
SOverlayUtils.hideOverlay();
95101
} else {

forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -963,16 +963,22 @@ public void popupMenuCanceled(PopupMenuEvent e) {
963963

964964
@Override
965965
public void showPromptMessage(final PlayerView playerView, final String message) {
966+
cancelWaitingTimer();
967+
cPrompt.setMessage(message);
968+
}
969+
public void showPromptMessageNoCancel(final PlayerView playerView, final String message) {
966970
cPrompt.setMessage(message);
967971
}
968972

969973
@Override
970974
public void showCardPromptMessage(PlayerView playerView, String message, CardView card) {
975+
cancelWaitingTimer();
971976
cPrompt.setMessage(message, card);
972977
}
973978

974979
// no override for now
975980
public void showPromptMessage(final PlayerView playerView, final String message, final CardView card ) {
981+
cancelWaitingTimer();
976982
cPrompt.setMessage(message,card);
977983
}
978984

forge-gui-desktop/src/main/java/forge/view/FNavigationBar.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.google.common.collect.Lists;
2525

2626
import forge.Singletons;
27+
import forge.gamemodes.match.AbstractGuiGame;
2728
import forge.gui.framework.FScreen;
2829
import forge.gui.framework.ILocalRepaint;
2930
import forge.localinstance.properties.ForgePreferences;
@@ -185,6 +186,11 @@ private void closeTab(final NavigationTab tab) {
185186
}
186187
}
187188

189+
public boolean hasNetGame() {
190+
return tabs.stream().filter(t -> t.getScreen().getController() instanceof AbstractGuiGame game
191+
&& game.isNetGame()).findAny().map(b -> true).orElse(false);
192+
}
193+
188194
@Override
189195
public void updateButtons() {
190196
super.updateButtons();
@@ -475,6 +481,10 @@ public void paintComponent(final Graphics g) {
475481
super.paintComponent(g);
476482
}
477483

484+
public FScreen getScreen() {
485+
return screen;
486+
}
487+
478488
private void updateTitle() {
479489
setText(screen.getTabCaption());
480490
}

forge-gui-mobile/src/forge/GuiMobile.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,11 @@ public void preventSystemSleep(boolean preventSleep) {
362362
Forge.getDeviceAdapter().preventSystemSleep(preventSleep);
363363
}
364364

365+
@Override
366+
public boolean hasNetGame() {
367+
return MatchController.instance.isNetGame();
368+
}
369+
365370
@Override
366371
public float getScreenScale() {
367372
return 1f;

forge-gui-mobile/src/forge/screens/constructed/LobbyScreen.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -602,8 +602,6 @@ public void update(final boolean fullUpdate) {
602602

603603
final boolean allowNetworking = lobby.isAllowNetworking();
604604

605-
GuiBase.setNetworkplay(allowNetworking);
606-
607605
setStartButtonAvailability();
608606

609607
for (int i = 0; i < cbPlayerCount.getSelectedItem(); i++) {

forge-gui-mobile/src/forge/screens/match/MatchController.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ public void refreshCardDetails(final Iterable<CardView> cards) {
143143

144144
@Override
145145
public void refreshField() {
146-
if(!GuiBase.isNetworkplay())
146+
if(!GuiBase.isNetworkplay(this))
147147
return;
148148
refreshCardDetails(null);
149149
}
@@ -182,7 +182,7 @@ public void openView(final TrackableCollection<PlayerView> myPlayers) {
182182
}
183183
}
184184
view = new MatchScreen(playerPanels);
185-
if(GuiBase.isNetworkplay())
185+
if(GuiBase.isNetworkplay(this))
186186
view.resetFields();
187187
clearSelectables(); //fix uncleared selection
188188

@@ -211,11 +211,16 @@ public void buildTouchListeners(final float screenX, final float screenY, final
211211

212212
@Override
213213
public void showPromptMessage(final PlayerView player, final String message) {
214+
cancelWaitingTimer();
215+
view.getPrompt(player).setMessage(message);
216+
}
217+
public void showPromptMessageNoCancel(final PlayerView player, final String message) {
214218
view.getPrompt(player).setMessage(message);
215219
}
216220

217221
@Override
218222
public void showCardPromptMessage(final PlayerView player, final String message, final CardView card) {
223+
cancelWaitingTimer();
219224
view.getPrompt(player).setMessage(message, card);
220225
}
221226

@@ -251,19 +256,19 @@ public void updatePhase(boolean saveState) {
251256
final VPhaseIndicator.PhaseLabel phaseLabel = view.getPlayerPanel(lastPlayer).getPhaseIndicator().getLabel(ph);
252257
if (phaseLabel != null)
253258
phaseLabel.setActive(true);
254-
if (GuiBase.isNetworkplay())
259+
if (GuiBase.isNetworkplay(this))
255260
getGameView().updateNeedsPhaseRedrawn(lastPlayer, PhaseType.CLEANUP);
256261
} else if (getGameView().getPlayerTurn() != null) {
257262
//set phaselabel
258263
final VPhaseIndicator.PhaseLabel phaseLabel = view.getPlayerPanel(getGameView().getPlayerTurn()).getPhaseIndicator().getLabel(ph);
259264
if (phaseLabel != null)
260265
phaseLabel.setActive(true);
261-
if (GuiBase.isNetworkplay())
266+
if (GuiBase.isNetworkplay(this))
262267
getGameView().updateNeedsPhaseRedrawn(getGameView().getPlayerTurn(), ph);
263268
}
264269
}
265270

266-
if(GuiBase.isNetworkplay())
271+
if(GuiBase.isNetworkplay(this))
267272
checkStack();
268273

269274
if (ph != null && saveState && ph.isMain()) {

forge-gui-mobile/src/forge/screens/match/MatchScreen.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ public MatchScreen(List<VPlayerPanel> playerPanels0) {
126126
e -> getGameController().selectButtonOk(),
127127
e -> getGameController().selectButtonCancel()));
128128

129-
if (humanCount < 2 || MatchController.instance.hotSeatMode() || GuiBase.isNetworkplay())
129+
if (humanCount < 2 || MatchController.instance.hotSeatMode() || GuiBase.isNetworkplay(MatchController.instance))
130130
topPlayerPrompt = null;
131131
else {
132132
//show top prompt if multiple human players and not playing in Hot Seat mode and not in network play
@@ -378,7 +378,7 @@ protected void drawOverlay(Graphics g) {
378378
if (devMenu.isVisible()) {
379379
try {
380380
//rollbackphase enable -- todo limit by gametype?
381-
devMenu.getChildAt(2).setEnabled(game.getPlayers().size() == 2 && game.getStack().size() == 0 && !GuiBase.isNetworkplay() && game.getPhase().isMain() && !game.getPlayerTurn().isAI());
381+
devMenu.getChildAt(2).setEnabled(game.getPlayers().size() == 2 && game.getStack().size() == 0 && !GuiBase.isNetworkplay(MatchController.instance) && game.getPhase().isMain() && !game.getPlayerTurn().isAI());
382382
} catch (Exception e) {/*NPE when the game hasn't started yet and you click dev mode*/}
383383
}
384384
}

0 commit comments

Comments
 (0)