diff --git a/forge-gui-desktop/src/main/java/forge/GuiDesktop.java b/forge-gui-desktop/src/main/java/forge/GuiDesktop.java index f6ae7f95d4b..b71cdd47306 100644 --- a/forge-gui-desktop/src/main/java/forge/GuiDesktop.java +++ b/forge-gui-desktop/src/main/java/forge/GuiDesktop.java @@ -33,6 +33,7 @@ import forge.gui.CardListChooser; import forge.gui.CardListViewer; import forge.gui.FThreads; +import forge.gui.GuiBase; import forge.gui.GuiChoose; import forge.gui.download.GuiDownloadService; import forge.gui.framework.FScreen; @@ -368,6 +369,11 @@ private static float initializeScreenScale() { } static float screenScale = initializeScreenScale(); + @Override + public boolean hasNetGame() { + return GuiBase.isNetworkplay(); // fallback to existing static flag + } + @Override public float getScreenScale() { return screenScale; diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/online/CSubmenuOnlineLobby.java b/forge-gui-desktop/src/main/java/forge/screens/home/online/CSubmenuOnlineLobby.java index 523b83d0cfe..2fcb1ded4cb 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/online/CSubmenuOnlineLobby.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/online/CSubmenuOnlineLobby.java @@ -86,10 +86,16 @@ private void join(final String url) { }); final ChatMessage result = NetConnectUtil.join(url, VSubmenuOnlineLobby.SINGLETON_INSTANCE, FNetOverlay.SINGLETON_INSTANCE); - if(Objects.equals(result.getMessage(), ForgeConstants.CLOSE_CONN_COMMAND)) { + String message = result.getMessage(); + if(Objects.equals(message, ForgeConstants.CLOSE_CONN_COMMAND)) { FOptionPane.showErrorDialog(Localizer.getInstance().getMessage("UnableConnectToServer", url)); SOverlayUtils.hideOverlay(); - } else if (Objects.equals(result.getMessage(), ForgeConstants.INVALID_HOST_COMMAND)) { + } else if (message != null && message.startsWith(ForgeConstants.CONN_ERROR_PREFIX)) { + // Show detailed connection error + String errorDetail = message.substring(ForgeConstants.CONN_ERROR_PREFIX.length()); + FOptionPane.showErrorDialog(errorDetail, Localizer.getInstance().getMessage("lblConnectionError")); + SOverlayUtils.hideOverlay(); + } else if (Objects.equals(message, ForgeConstants.INVALID_HOST_COMMAND)) { FOptionPane.showErrorDialog(Localizer.getInstance().getMessage("lblDetectedInvalidHostAddress", url)); SOverlayUtils.hideOverlay(); } else { diff --git a/forge-gui-mobile/src/forge/GuiMobile.java b/forge-gui-mobile/src/forge/GuiMobile.java index 3ebac46e967..4acf6f415b9 100644 --- a/forge-gui-mobile/src/forge/GuiMobile.java +++ b/forge-gui-mobile/src/forge/GuiMobile.java @@ -362,6 +362,11 @@ public void preventSystemSleep(boolean preventSleep) { Forge.getDeviceAdapter().preventSystemSleep(preventSleep); } + @Override + public boolean hasNetGame() { + return GuiBase.isNetworkplay(); + } + @Override public float getScreenScale() { return 1f; diff --git a/forge-gui-mobile/src/forge/screens/match/MatchController.java b/forge-gui-mobile/src/forge/screens/match/MatchController.java index 94e8b9ed986..af9740d2832 100644 --- a/forge-gui-mobile/src/forge/screens/match/MatchController.java +++ b/forge-gui-mobile/src/forge/screens/match/MatchController.java @@ -143,7 +143,7 @@ public void refreshCardDetails(final Iterable cards) { @Override public void refreshField() { - if(!GuiBase.isNetworkplay()) + if(!GuiBase.isNetworkplay(this)) return; refreshCardDetails(null); } @@ -182,7 +182,7 @@ public void openView(final TrackableCollection myPlayers) { } } view = new MatchScreen(playerPanels); - if(GuiBase.isNetworkplay()) + if(GuiBase.isNetworkplay(this)) view.resetFields(); clearSelectables(); //fix uncleared selection @@ -251,19 +251,19 @@ public void updatePhase(boolean saveState) { final VPhaseIndicator.PhaseLabel phaseLabel = view.getPlayerPanel(lastPlayer).getPhaseIndicator().getLabel(ph); if (phaseLabel != null) phaseLabel.setActive(true); - if (GuiBase.isNetworkplay()) + if (GuiBase.isNetworkplay(this)) getGameView().updateNeedsPhaseRedrawn(lastPlayer, PhaseType.CLEANUP); } else if (getGameView().getPlayerTurn() != null) { //set phaselabel final VPhaseIndicator.PhaseLabel phaseLabel = view.getPlayerPanel(getGameView().getPlayerTurn()).getPhaseIndicator().getLabel(ph); if (phaseLabel != null) phaseLabel.setActive(true); - if (GuiBase.isNetworkplay()) + if (GuiBase.isNetworkplay(this)) getGameView().updateNeedsPhaseRedrawn(getGameView().getPlayerTurn(), ph); } } - if(GuiBase.isNetworkplay()) + if(GuiBase.isNetworkplay(this)) checkStack(); if (ph != null && saveState && ph.isMain()) { diff --git a/forge-gui-mobile/src/forge/screens/online/OnlineLobbyScreen.java b/forge-gui-mobile/src/forge/screens/online/OnlineLobbyScreen.java index e911d9f135b..de05337d85a 100644 --- a/forge-gui-mobile/src/forge/screens/online/OnlineLobbyScreen.java +++ b/forge-gui-mobile/src/forge/screens/online/OnlineLobbyScreen.java @@ -107,10 +107,16 @@ public void onActivate() { final IOnlineChatInterface chatInterface = (IOnlineChatInterface)OnlineScreen.Chat.getScreen(); if (joinServer) { result[0] = NetConnectUtil.join(url, OnlineLobbyScreen.this, chatInterface); - if (result[0].getMessage() == ForgeConstants.CLOSE_CONN_COMMAND) { //this message is returned via netconnectutil on exception + String message = result[0].getMessage(); + if (ForgeConstants.CLOSE_CONN_COMMAND.equals(message)) { //this message is returned via netconnectutil on exception closeConn(Forge.getLocalizer().getMessage("UnableConnectToServer", url)); return; - } else if (result[0].getMessage() == ForgeConstants.INVALID_HOST_COMMAND) { + } else if (message != null && message.startsWith(ForgeConstants.CONN_ERROR_PREFIX)) { + // Show detailed connection error + String errorDetail = message.substring(ForgeConstants.CONN_ERROR_PREFIX.length()); + closeConn(errorDetail); + return; + } else if (ForgeConstants.INVALID_HOST_COMMAND.equals(message)) { closeConn(Forge.getLocalizer().getMessage("lblDetectedInvalidHostAddress", url)); return; } diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index 1bc0b94a296..c78a42aab4e 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -313,6 +313,12 @@ AresetMatchScreenLayout=This will reset the layout of the Match screen.\n If you TresetMatchScreenLayout=Reset Match Screen Layout OKresetMatchScreenLayout=Match Screen layout has been reset. UnableConnectToServer=Unable to connect to server with host {0}. +lblConnectionError=Connection Error +lblConnectionFailedTo=Failed to connect to {0}:{1} +lblConnectionRefused=Connection refused. Please verify:\n- The host is running and accepting connections\n- The port number is correct\n- No firewall is blocking the connection +lblUnknownHost=Unknown host. Please check the hostname or IP address is correct. +lblConnectionTimeout=Connection timed out. The server may be unreachable or behind a firewall. +lblNoRouteToHost=No route to host. Please check your network connection. #EMenuGroup.java lblSanctionedFormats=Sanctioned Formats lblOnlineMultiplayer=Online Multiplayer @@ -1520,6 +1526,8 @@ lblConcedeCurrentGame=This will concede the current game and you will lose.\n\nC lblConcedeTitle=Concede Game? lblConcede=Concede lblWaitingforActions=Waiting for actions... +lblWaitingForPlayer=Waiting for {0}... +lblWaitingForPlayerWithTime=Waiting for {0}... ({1}) lblCloseGameSpectator=This will close this game and you will not be able to resume watching it.\n\nClose anyway? lblCloseGame=Close Game? lblWaitingForOpponent=Waiting for opponent... diff --git a/forge-gui/src/main/java/forge/gamemodes/match/AbstractGuiGame.java b/forge-gui/src/main/java/forge/gamemodes/match/AbstractGuiGame.java index 333981e1311..59e8cf32a50 100644 --- a/forge-gui/src/main/java/forge/gamemodes/match/AbstractGuiGame.java +++ b/forge-gui/src/main/java/forge/gamemodes/match/AbstractGuiGame.java @@ -446,10 +446,12 @@ public final boolean mayAutoPass(final PlayerView player) { private Timer awaitNextInputTimer; private TimerTask awaitNextInputTask; + private volatile long awaitStartTime; @Override public final void awaitNextInput() { checkAwaitNextInputTimer(); + awaitStartTime = System.currentTimeMillis(); //delay updating prompt to await next input briefly so buttons don't flicker disabled then enabled awaitNextInputTask = new TimerTask() { @Override @@ -459,7 +461,12 @@ public void run() { synchronized (awaitNextInputTimer) { if (awaitNextInputTask != null) { updatePromptForAwait(getCurrentPlayer()); - awaitNextInputTask = null; + // In network games, reschedule every second to update elapsed time + if (GuiBase.isNetworkplay(AbstractGuiGame.this)) { + scheduleTimerUpdate(); + } else { + awaitNextInputTask = null; + } } } }); @@ -467,6 +474,29 @@ public void run() { }; awaitNextInputTimer.schedule(awaitNextInputTask, 250); } + + private void scheduleTimerUpdate() { + awaitNextInputTask = new TimerTask() { + @Override + public void run() { + FThreads.invokeInEdtLater(() -> { + checkAwaitNextInputTimer(); + synchronized (awaitNextInputTimer) { + if (awaitNextInputTask != null) { + showPromptMessage(getCurrentPlayer(), getWaitingMessage(getCurrentPlayer())); + scheduleTimerUpdate(); + } + } + }); + } + }; + try { + awaitNextInputTimer.schedule(awaitNextInputTask, 1000); + } catch (final IllegalStateException ex) { + // Timer was cancelled between check and schedule + } + } + private void checkAwaitNextInputTimer() { if (awaitNextInputTimer == null) { String name = "?"; @@ -477,10 +507,68 @@ private void checkAwaitNextInputTimer() { } protected final void updatePromptForAwait(final PlayerView playerView) { - showPromptMessage(playerView, Localizer.getInstance().getMessage("lblWaitingForOpponent")); + showPromptMessage(playerView, getWaitingMessage(playerView)); updateButtons(playerView, false, false, false); } + private String getWaitingMessage(final PlayerView forPlayer) { + Localizer localizer = Localizer.getInstance(); + + if (GuiBase.isNetworkplay(this) && gameView != null && !gameView.isGameOver()) { + String name = findWaitingForPlayerName(forPlayer); + if (name != null) { + String timeStr = getElapsedTimeString(); + if (timeStr != null) { + return localizer.getMessage("lblWaitingForPlayerWithTime", name, timeStr); + } + return localizer.getMessage("lblWaitingForPlayer", name); + } + } + + return localizer.getMessage("lblWaitingForOpponent"); + } + + private String findWaitingForPlayerName(final PlayerView forPlayer) { + if (gameView.getPlayers() != null) { + for (PlayerView pv : gameView.getPlayers()) { + if (pv.getHasPriority() && (forPlayer == null || pv.getId() != forPlayer.getId())) { + return pv.getName(); + } + } + } + // Fallback to turn player during mulligan/setup + PlayerView turnPlayer = gameView.getPlayerTurn(); + if (turnPlayer != null && (forPlayer == null || turnPlayer.getId() != forPlayer.getId())) { + return turnPlayer.getName(); + } + // Fallback to any non-local player + if (gameView.getPlayers() != null) { + for (PlayerView pv : gameView.getPlayers()) { + if (forPlayer != null && pv.getId() == forPlayer.getId()) { + continue; + } + if (!isLocalPlayer(pv)) { + return pv.getName(); + } + } + } + return null; + } + + private String getElapsedTimeString() { + if (awaitStartTime == 0) { + return null; + } + long elapsedSec = (System.currentTimeMillis() - awaitStartTime) / 1000; + if (elapsedSec < 2) { + return null; + } + if (elapsedSec < 60) { + return elapsedSec + "s"; + } + return String.format("%d:%02d", elapsedSec / 60, elapsedSec % 60); + } + @Override public final void cancelAwaitNextInput() { if (awaitNextInputTimer == null) { @@ -495,6 +583,7 @@ public final void cancelAwaitNextInput() { awaitNextInputTask = null; } } + awaitStartTime = 0; } @Override diff --git a/forge-gui/src/main/java/forge/gamemodes/match/input/InputBase.java b/forge-gui/src/main/java/forge/gamemodes/match/input/InputBase.java index f59d1ee0d1a..3cb79709b7d 100644 --- a/forge-gui/src/main/java/forge/gamemodes/match/input/InputBase.java +++ b/forge-gui/src/main/java/forge/gamemodes/match/input/InputBase.java @@ -124,13 +124,13 @@ protected final void showMessage(final String message) { controller.getGui().showPromptMessage(getOwner(), message); } protected final void showMessage(final String message, final SpellAbilityView sav) { - if (GuiBase.isNetworkplay()) //todo additional check to pass this + if (GuiBase.isNetworkplay(controller.getGui())) //todo additional check to pass this controller.getGui().showPromptMessage(getOwner(), message); else controller.getGui().showCardPromptMessage(getOwner(), message, sav.getHostCard()); } protected final void showMessage(final String message, final CardView card) { - if (GuiBase.isNetworkplay()) //todo additional check to pass this + if (GuiBase.isNetworkplay(controller.getGui())) //todo additional check to pass this controller.getGui().showPromptMessage(getOwner(), message); else controller.getGui().showCardPromptMessage(getOwner(), message, card); diff --git a/forge-gui/src/main/java/forge/gamemodes/net/NetConnectUtil.java b/forge-gui/src/main/java/forge/gamemodes/net/NetConnectUtil.java index 02bda81daa1..06f3569fff5 100644 --- a/forge-gui/src/main/java/forge/gamemodes/net/NetConnectUtil.java +++ b/forge-gui/src/main/java/forge/gamemodes/net/NetConnectUtil.java @@ -172,10 +172,47 @@ public ClientGameLobby getLobby() { client.connect(); } catch (Exception ex) { - //return a message to close the connection so we will not crash... - return new ChatMessage(null, ForgeConstants.CLOSE_CONN_COMMAND); + // Return error with details for GUI display + String errorDetail = getConnectionErrorMessage(ex, hostname, port); + return new ChatMessage(null, ForgeConstants.CONN_ERROR_PREFIX + errorDetail); } return new ChatMessage(null, Localizer.getInstance().getMessage("lblConnectedIPPort", hostname, String.valueOf(port))); } + + /** + * Generate a user-friendly error message for connection failures. + */ + private static String getConnectionErrorMessage(Exception ex, String hostname, int port) { + Localizer localizer = Localizer.getInstance(); + StringBuilder sb = new StringBuilder(); + + // Get the root cause for better error messages + Throwable cause = ex.getCause() != null ? ex.getCause() : ex; + String causeName = cause.getClass().getSimpleName(); + + sb.append(localizer.getMessage("lblConnectionFailedTo", hostname, String.valueOf(port))); + sb.append("\n\n"); + + // Provide specific messages for common error types + if (causeName.contains("ConnectException") || causeName.contains("ConnectionRefused")) { + sb.append(localizer.getMessage("lblConnectionRefused")); + } else if (causeName.contains("UnknownHost")) { + sb.append(localizer.getMessage("lblUnknownHost")); + } else if (causeName.contains("Timeout") || causeName.contains("TimedOut")) { + sb.append(localizer.getMessage("lblConnectionTimeout")); + } else if (causeName.contains("NoRouteToHost")) { + sb.append(localizer.getMessage("lblNoRouteToHost")); + } else { + // Generic error with the exception message + String msg = cause.getMessage(); + if (msg != null && !msg.isEmpty()) { + sb.append(msg); + } else { + sb.append(causeName); + } + } + + return sb.toString(); + } } diff --git a/forge-gui/src/main/java/forge/gamemodes/net/server/FServerManager.java b/forge-gui/src/main/java/forge/gamemodes/net/server/FServerManager.java index ea4852fc088..ab61840833c 100644 --- a/forge-gui/src/main/java/forge/gamemodes/net/server/FServerManager.java +++ b/forge-gui/src/main/java/forge/gamemodes/net/server/FServerManager.java @@ -234,6 +234,34 @@ public void updateLobbyState() { public void updateSlot(final int index, final UpdateLobbyPlayerEvent event) { localLobby.applyToSlot(index, event); + + if (event.getReady() != null) { + broadcastReadyState(localLobby.getSlot(index).getName(), event.getReady()); + } + } + + private void broadcastReadyState(String playerName, boolean isReady) { + int readyCount = 0; + int totalPlayers = 0; + for (int i = 0; i < localLobby.getNumberOfSlots(); i++) { + LobbySlot slot = localLobby.getSlot(i); + if (slot.getType() == LobbySlotType.LOCAL || slot.getType() == LobbySlotType.REMOTE) { + totalPlayers++; + if (slot.isReady()) { + readyCount++; + } + } + } + if (isReady) { + broadcast(new MessageEvent(String.format("%s is ready (%d/%d players ready)", + playerName, readyCount, totalPlayers))); + if (readyCount == totalPlayers && totalPlayers > 1) { + broadcast(new MessageEvent("All players ready to start game!")); + } + } else { + broadcast(new MessageEvent(String.format("%s is not ready (%d/%d players ready)", + playerName, readyCount, totalPlayers))); + } } public IGuiGame getGui(final int index) { @@ -339,7 +367,14 @@ private class MessageHandler extends ChannelInboundHandlerAdapter { public final void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception { final RemoteClient client = clients.get(ctx.channel()); if (msg instanceof MessageEvent) { - broadcast(new MessageEvent(client.getUsername(), ((MessageEvent) msg).getMessage())); + String username = client.getUsername(); + String message = ((MessageEvent) msg).getMessage(); + + // Append (Host) indicator for the host player + if (client.getIndex() == 0) { + username = username + " (Host)"; + } + broadcast(new MessageEvent(username, message)); } super.channelRead(ctx, msg); } @@ -359,12 +394,31 @@ public void channelActive(final ChannelHandlerContext ctx) throws Exception { public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception { final RemoteClient client = clients.get(ctx.channel()); if (msg instanceof LoginEvent) { - final String username = ((LoginEvent) msg).getUsername(); + final LoginEvent event = (LoginEvent) msg; + final String username = event.getUsername(); client.setUsername(username); - broadcast(new MessageEvent(String.format("%s joined the room", username))); + if (client.getIndex() == 0) { + broadcast(new MessageEvent(String.format("Lobby hosted by %s", username))); + } else { + broadcast(new MessageEvent(String.format("%s joined the lobby", username))); + } updateLobbyState(); } else if (msg instanceof UpdateLobbyPlayerEvent) { - localLobby.applyToSlot(client.getIndex(), (UpdateLobbyPlayerEvent) msg); + UpdateLobbyPlayerEvent updateEvent = (UpdateLobbyPlayerEvent) msg; + localLobby.applyToSlot(client.getIndex(), updateEvent); + if (updateEvent.getName() != null) { + String oldName = client.getUsername(); + String newName = updateEvent.getName(); + if (!newName.equals(oldName)) { + client.setUsername(newName); + broadcast(new MessageEvent(String.format("%s changed their name to %s", oldName, newName))); + } + } + if (updateEvent.getReady() != null) { + broadcastReadyState(client.getUsername(), updateEvent.getReady()); + } + // Return to prevent duplicate processing by LobbyInputHandler + return; } super.channelRead(ctx, msg); } @@ -386,10 +440,9 @@ public void channelRead(final ChannelHandlerContext ctx, final Object msg) throw } } else if (msg instanceof UpdateLobbyPlayerEvent) { updateSlot(client.getIndex(), (UpdateLobbyPlayerEvent) msg); - } else if (msg instanceof MessageEvent) { - final MessageEvent event = (MessageEvent) msg; - lobbyListener.message(event.getSource(), event.getMessage()); } + // Note: MessageEvent is handled by MessageHandler, not here + // to avoid duplicate display on host's chat super.channelRead(ctx, msg); } } @@ -400,7 +453,7 @@ public void channelInactive(final ChannelHandlerContext ctx) throws Exception { final RemoteClient client = clients.remove(ctx.channel()); final String username = client.getUsername(); localLobby.disconnectPlayer(client.getIndex()); - broadcast(new MessageEvent(String.format("%s left the room", username))); + broadcast(new MessageEvent(String.format("%s left the lobby", username))); broadcast(new LogoutEvent(username)); super.channelInactive(ctx); } diff --git a/forge-gui/src/main/java/forge/gamemodes/net/server/NetGuiGame.java b/forge-gui/src/main/java/forge/gamemodes/net/server/NetGuiGame.java index 0a0c7b54963..3ef59f0f582 100644 --- a/forge-gui/src/main/java/forge/gamemodes/net/server/NetGuiGame.java +++ b/forge-gui/src/main/java/forge/gamemodes/net/server/NetGuiGame.java @@ -314,6 +314,9 @@ public boolean isUiSetToSkipPhase(final PlayerView playerTurn, final PhaseType p return sendAndWait(ProtocolMethod.isUiSetToSkipPhase, playerTurn, phase); } + @Override + public boolean isNetGame() { return true; } + @Override protected void updateCurrentPlayer(final PlayerView player) { // TODO Auto-generated method stub diff --git a/forge-gui/src/main/java/forge/gui/GuiBase.java b/forge-gui/src/main/java/forge/gui/GuiBase.java index 4007becd4b2..263e6d8c5cf 100644 --- a/forge-gui/src/main/java/forge/gui/GuiBase.java +++ b/forge-gui/src/main/java/forge/gui/GuiBase.java @@ -2,6 +2,7 @@ import forge.util.HWInfo; import forge.gui.interfaces.IGuiBase; +import forge.gui.interfaces.IGuiGame; import forge.localinstance.properties.ForgePreferences; public class GuiBase { @@ -61,6 +62,15 @@ public static String getDownloadsDir() { public static int getDeviceRAM() { return deviceRAM; } public static boolean isNetworkplay() { return networkplay; } + public static boolean isNetworkplay(IGuiGame game) { + if (game != null) { + // query AbstractGuiGame implementation if provided + return game.isNetGame(); + } + // both IGuiBase implementations should have (at least indirect) access to matches + // to check all available IGuiGame + return getInterface().hasNetGame(); + } public static void setNetworkplay(boolean value) { networkplay = value; } public static boolean hasPropertyConfig() { return propertyConfig; } diff --git a/forge-gui/src/main/java/forge/gui/control/FControlGameEventHandler.java b/forge-gui/src/main/java/forge/gui/control/FControlGameEventHandler.java index d0319ca3135..86c79c4033f 100644 --- a/forge-gui/src/main/java/forge/gui/control/FControlGameEventHandler.java +++ b/forge-gui/src/main/java/forge/gui/control/FControlGameEventHandler.java @@ -393,7 +393,7 @@ public Void visit(final GameEventCombatEnded event) { @Override public Void visit(final GameEventCombatUpdate event) { - if (!GuiBase.isNetworkplay()) + if (!GuiBase.isNetworkplay(matchController)) return null; //not needed if single player only... final CardCollection cards = new CardCollection(); diff --git a/forge-gui/src/main/java/forge/gui/interfaces/IGuiBase.java b/forge-gui/src/main/java/forge/gui/interfaces/IGuiBase.java index fcad3f9218a..c71f2376b08 100644 --- a/forge-gui/src/main/java/forge/gui/interfaces/IGuiBase.java +++ b/forge-gui/src/main/java/forge/gui/interfaces/IGuiBase.java @@ -65,4 +65,7 @@ public interface IGuiBase { void preventSystemSleep(boolean preventSleep); float getScreenScale(); UpnpServiceConfiguration getUpnpPlatformService(); + + /** Returns true if any currently active game is a network game. */ + boolean hasNetGame(); } \ No newline at end of file diff --git a/forge-gui/src/main/java/forge/gui/interfaces/IGuiGame.java b/forge-gui/src/main/java/forge/gui/interfaces/IGuiGame.java index b56468dcc52..66ff5d8c280 100644 --- a/forge-gui/src/main/java/forge/gui/interfaces/IGuiGame.java +++ b/forge-gui/src/main/java/forge/gui/interfaces/IGuiGame.java @@ -278,4 +278,7 @@ public interface IGuiGame { void clearAutoYields(); void setCurrentPlayer(PlayerView player); + + /** Returns true if this game instance is a network game. */ + default boolean isNetGame() { return false; } } diff --git a/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java b/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java index 5febf260079..920f85dc5a5 100644 --- a/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java +++ b/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java @@ -272,6 +272,7 @@ public final class ForgeConstants { public static final String ITEM_VIEW_PREFS_FILE = USER_PREFS_DIR + "item_view.preferences"; public static final String CLOSE_CONN_COMMAND = "<<_EM_ESOLC_<<"; public static final String INVALID_HOST_COMMAND = "<<_TSOH_DILAVNI_<<"; + public static final String CONN_ERROR_PREFIX = "<<_CONN_ERROR_>>:"; // data that has defaults in the program dir but overrides/additions in the user dir private static final String _DEFAULTS_DIR = RES_DIR + "defaults" + PATH_SEPARATOR; diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java index 7aef50cf68e..a9d0d28701e 100644 --- a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java +++ b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java @@ -1002,7 +1002,7 @@ public ImmutablePair arrangeForScry(final CardCo tempShowCards(topN); if (FModel.getPreferences().getPrefBoolean(FPref.UI_SELECT_FROM_CARD_DISPLAYS) && - (!GuiBase.getInterface().isLibgdxPort()) && (!GuiBase.isNetworkplay())) { //prevent crash for desktop vs mobile port it will crash the netplay since mobile doesnt have manipulatecardlist, send the alternate below + (!GuiBase.getInterface().isLibgdxPort()) && (!GuiBase.isNetworkplay(getGui()))) { //prevent crash for desktop vs mobile port it will crash the netplay since mobile doesnt have manipulatecardlist, send the alternate below CardCollectionView cardList = player.getCardsIn(ZoneType.Library); ImmutablePair result = arrangeForMove(localizer.getMessage("lblMoveCardstoToporBbottomofLibrary"), cardList, topN, true, true); @@ -1942,7 +1942,7 @@ public ReplacementEffect chooseSingleReplacementEffect(final List re.getId() == rev.getId()).findAny().orElse(first); @@ -1963,7 +1963,7 @@ public StaticAbility chooseSingleStaticAbility(final String prompt, final List st.getId() == stv.getId()).findAny().orElse(first);