Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,6 @@ forge-gui/tools/PerSetTrackingResults
# Ignore python temporaries
__pycache__
*.pyc

# Ignore Claude configuration
.claude
8 changes: 8 additions & 0 deletions forge-gui-desktop/src/main/java/forge/GuiDesktop.java
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,14 @@ private static float initializeScreenScale() {
}
static float screenScale = initializeScreenScale();

@Override
public boolean hasNetGame() {
if (Singletons.getView() == null || Singletons.getView().getNavigationBar() == null) {
return false;
}
return Singletons.getView().getNavigationBar().hasNetGame();
}

@Override
public float getScreenScale() {
return screenScale;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
import forge.gamemodes.match.LobbySlotType;
import forge.gamemodes.net.event.UpdateLobbyPlayerEvent;
import forge.gui.CardDetailPanel;
import forge.gui.GuiBase;
import forge.gui.SwingPrefBinders;
import forge.gui.interfaces.ILobbyView;
import forge.gui.util.SOptionPane;
Expand Down Expand Up @@ -247,8 +246,6 @@ public void update(final boolean fullUpdate) {

final boolean allowNetworking = lobby.isAllowNetworking();

GuiBase.setNetworkplay(allowNetworking);

ImmutableList<VariantCheckBox> vntBoxes = null;
if (allowNetworking) {
vntBoxes = vntBoxesNetwork;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -963,16 +963,22 @@ public void popupMenuCanceled(PopupMenuEvent e) {

@Override
public void showPromptMessage(final PlayerView playerView, final String message) {
cancelWaitingTimer();
cPrompt.setMessage(message);
}
public void showPromptMessageNoCancel(final PlayerView playerView, final String message) {
cPrompt.setMessage(message);
}

@Override
public void showCardPromptMessage(PlayerView playerView, String message, CardView card) {
cancelWaitingTimer();
cPrompt.setMessage(message, card);
}

// no override for now
public void showPromptMessage(final PlayerView playerView, final String message, final CardView card ) {
cancelWaitingTimer();
cPrompt.setMessage(message,card);
}

Expand Down
10 changes: 10 additions & 0 deletions forge-gui-desktop/src/main/java/forge/view/FNavigationBar.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.google.common.collect.Lists;

import forge.Singletons;
import forge.gamemodes.match.AbstractGuiGame;
import forge.gui.framework.FScreen;
import forge.gui.framework.ILocalRepaint;
import forge.localinstance.properties.ForgePreferences;
Expand Down Expand Up @@ -185,6 +186,11 @@ private void closeTab(final NavigationTab tab) {
}
}

public boolean hasNetGame() {
return tabs.stream().filter(t -> t.getScreen().getController() instanceof AbstractGuiGame game
&& game.isNetGame()).findAny().map(b -> true).orElse(false);
}

@Override
public void updateButtons() {
super.updateButtons();
Expand Down Expand Up @@ -475,6 +481,10 @@ public void paintComponent(final Graphics g) {
super.paintComponent(g);
}

public FScreen getScreen() {
return screen;
}

private void updateTitle() {
setText(screen.getTabCaption());
}
Expand Down
5 changes: 5 additions & 0 deletions forge-gui-mobile/src/forge/GuiMobile.java
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,11 @@ public void preventSystemSleep(boolean preventSleep) {
Forge.getDeviceAdapter().preventSystemSleep(preventSleep);
}

@Override
public boolean hasNetGame() {
return MatchController.instance.isNetGame();
}

@Override
public float getScreenScale() {
return 1f;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -602,8 +602,6 @@ public void update(final boolean fullUpdate) {

final boolean allowNetworking = lobby.isAllowNetworking();

GuiBase.setNetworkplay(allowNetworking);

setStartButtonAvailability();

for (int i = 0; i < cbPlayerCount.getSelectedItem(); i++) {
Expand Down
15 changes: 10 additions & 5 deletions forge-gui-mobile/src/forge/screens/match/MatchController.java
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ public void refreshCardDetails(final Iterable<CardView> cards) {

@Override
public void refreshField() {
if(!GuiBase.isNetworkplay())
if(!GuiBase.isNetworkplay(this))
return;
refreshCardDetails(null);
}
Expand Down Expand Up @@ -182,7 +182,7 @@ public void openView(final TrackableCollection<PlayerView> myPlayers) {
}
}
view = new MatchScreen(playerPanels);
if(GuiBase.isNetworkplay())
if(GuiBase.isNetworkplay(this))
view.resetFields();
clearSelectables(); //fix uncleared selection

Expand Down Expand Up @@ -211,11 +211,16 @@ public void buildTouchListeners(final float screenX, final float screenY, final

@Override
public void showPromptMessage(final PlayerView player, final String message) {
cancelWaitingTimer();
view.getPrompt(player).setMessage(message);
}
public void showPromptMessageNoCancel(final PlayerView player, final String message) {
view.getPrompt(player).setMessage(message);
}

@Override
public void showCardPromptMessage(final PlayerView player, final String message, final CardView card) {
cancelWaitingTimer();
view.getPrompt(player).setMessage(message, card);
}

Expand Down Expand Up @@ -251,19 +256,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()) {
Expand Down
4 changes: 2 additions & 2 deletions forge-gui-mobile/src/forge/screens/match/MatchScreen.java
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ public MatchScreen(List<VPlayerPanel> playerPanels0) {
e -> getGameController().selectButtonOk(),
e -> getGameController().selectButtonCancel()));

if (humanCount < 2 || MatchController.instance.hotSeatMode() || GuiBase.isNetworkplay())
if (humanCount < 2 || MatchController.instance.hotSeatMode() || GuiBase.isNetworkplay(MatchController.instance))
topPlayerPrompt = null;
else {
//show top prompt if multiple human players and not playing in Hot Seat mode and not in network play
Expand Down Expand Up @@ -378,7 +378,7 @@ protected void drawOverlay(Graphics g) {
if (devMenu.isVisible()) {
try {
//rollbackphase enable -- todo limit by gametype?
devMenu.getChildAt(2).setEnabled(game.getPlayers().size() == 2 && game.getStack().size() == 0 && !GuiBase.isNetworkplay() && game.getPhase().isMain() && !game.getPlayerTurn().isAI());
devMenu.getChildAt(2).setEnabled(game.getPlayers().size() == 2 && game.getStack().size() == 0 && !GuiBase.isNetworkplay(MatchController.instance) && game.getPhase().isMain() && !game.getPlayerTurn().isAI());
} catch (Exception e) {/*NPE when the game hasn't started yet and you click dev mode*/}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ protected void startClip(Graphics g) {

@Override
public String getActivateAction(int index) {
if (!GuiBase.isNetworkplay()) {
if (!GuiBase.isNetworkplay(MatchController.instance)) {
//causes lag on netplay client side, also index shouldn't be out of bounds
if (index >= 0 && index < orderedCards.get().size())
return MatchController.instance.getGameController().getActivateDescription(orderedCards.get().get(index));
Expand Down
10 changes: 8 additions & 2 deletions forge-gui-mobile/src/forge/screens/online/OnlineLobbyScreen.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
7 changes: 7 additions & 0 deletions forge-gui/res/languages/en-US.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1520,6 +1526,7 @@ 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}...
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...
Expand Down
84 changes: 84 additions & 0 deletions forge-gui/src/main/java/forge/gamemodes/match/AbstractGuiGame.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,19 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards {
private PlaybackSpeed playbackSpeed = PlaybackSpeed.NORMAL;
private String daytime = null;
private boolean ignoreConcedeChain = false;
private boolean networkGame = false;

private java.util.Timer waitingTimer;
private long waitingStartTime;

@Override
public boolean isNetGame() {
return networkGame;
}
@Override
public void setNetGame() {
networkGame = true;
}

public final boolean hasLocalPlayers() {
return !gameControllers.isEmpty();
Expand Down Expand Up @@ -459,6 +472,9 @@ public void run() {
synchronized (awaitNextInputTimer) {
if (awaitNextInputTask != null) {
updatePromptForAwait(getCurrentPlayer());
if (GuiBase.isNetworkplay(AbstractGuiGame.this)) {
showWaitingTimer(getCurrentPlayer(), findWaitingForPlayerName(getCurrentPlayer()));
}
awaitNextInputTask = null;
}
}
Expand All @@ -467,6 +483,7 @@ public void run() {
};
awaitNextInputTimer.schedule(awaitNextInputTask, 250);
}

private void checkAwaitNextInputTimer() {
if (awaitNextInputTimer == null) {
String name = "?";
Expand All @@ -481,6 +498,72 @@ protected final void updatePromptForAwait(final PlayerView playerView) {
updateButtons(playerView, false, false, false);
}

@Override
public void showWaitingTimer(final PlayerView forPlayer, final String waitingForPlayerName) {
cancelWaitingTimer();
if (waitingForPlayerName == null) {
return;
}
this.waitingStartTime = System.currentTimeMillis();
waitingTimer = new java.util.Timer("waitingTimer");
waitingTimer.schedule(new java.util.TimerTask() {
@Override
public void run() {
FThreads.invokeInEdtLater(() -> updateWaitingDisplay(forPlayer, waitingForPlayerName));
}
}, 1000, 1000);
}

private void updateWaitingDisplay(final PlayerView forPlayer, final String waitingForPlayerName) {
long elapsedSec = (System.currentTimeMillis() - waitingStartTime) / 1000;
if (elapsedSec < 2) {
return;
}
String timeStr;
if (elapsedSec < 60) {
timeStr = elapsedSec + "s";
} else {
timeStr = String.format("%d:%02d", elapsedSec / 60, elapsedSec % 60);
}
showPromptMessageNoCancel(forPlayer, Localizer.getInstance().getMessage("lblWaitingForPlayer", waitingForPlayerName) + " (" + timeStr + ")");
}

protected void cancelWaitingTimer() {
if (waitingTimer != null) {
waitingTimer.cancel();
waitingTimer = null;
}
}

public void showPromptMessageNoCancel(final PlayerView playerView, final String message) {}

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;
}

@Override
public final void cancelAwaitNextInput() {
if (awaitNextInputTimer == null) {
Expand All @@ -495,6 +578,7 @@ public final void cancelAwaitNextInput() {
awaitNextInputTask = null;
}
}
cancelWaitingTimer();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading