From 6bc69d093507d9c41cabe58334e052e96135fd2c Mon Sep 17 00:00:00 2001 From: elcnesh Date: Thu, 2 Apr 2015 08:56:31 +0000 Subject: [PATCH] Update stability and fix issues in lobby and network games. - Fix closing the lobby/client so that it notifies the clients/server. - Improve deck selection in lobby (no longer adds Planes to deck and saves) - Improve responsiveness of lobby over network - In 2-player game over network, always assign local player to slot 0 - Warn when starting a commander deck without a commander - Put poison counters next to life total (frees up space in the details panel) --- .../src/main/java/forge/control/FControl.java | 1 + .../main/java/forge/screens/home/VLobby.java | 11 +- .../home/online/CSubmenuOnlineLobby.java | 14 +- .../screens/home/online/VOnlineLobby.java | 27 +++- .../java/forge/screens/match/CMatchUI.java | 22 +-- .../forge/screens/match/views/VField.java | 85 ++++++++---- .../toolbox/special/PlayerDetailsPanel.java | 127 +++++++++--------- .../java/forge/interfaces/ILobbyListener.java | 1 + .../src/main/java/forge/match/GameLobby.java | 61 +++++---- .../src/main/java/forge/match/LobbySlot.java | 5 +- .../src/main/java/forge/net/FGameClient.java | 12 ++ .../main/java/forge/net/FServerManager.java | 7 + 12 files changed, 235 insertions(+), 138 deletions(-) diff --git a/forge-gui-desktop/src/main/java/forge/control/FControl.java b/forge-gui-desktop/src/main/java/forge/control/FControl.java index e212a29fec8..eb0b77199cf 100644 --- a/forge-gui-desktop/src/main/java/forge/control/FControl.java +++ b/forge-gui-desktop/src/main/java/forge/control/FControl.java @@ -110,6 +110,7 @@ public enum FControl implements KeyEventDispatcher { } } } + /** *

* FControl. diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java b/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java index ae9fd8ad681..5bcfb8d6bb3 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java @@ -239,7 +239,8 @@ public class VLobby implements IUpdateable { isNewPanel = true; } - panel.setType(slot.getType()); + final LobbySlotType type = slot.getType(); + panel.setType(type); panel.setPlayerName(slot.getName()); panel.setAvatar(slot.getAvatarIndex()); panel.setTeam(slot.getTeam()); @@ -253,7 +254,7 @@ public class VLobby implements IUpdateable { final FDeckChooser deckChooser = getDeckChooser(i); deckChooser.setIsAi(slot.getType() == LobbySlotType.AI); - if (fullUpdate) { + if (fullUpdate && (type == LobbySlotType.LOCAL || type == LobbySlotType.AI)) { selectDeck(i); } if (isNewPanel) { @@ -298,7 +299,9 @@ public class VLobby implements IUpdateable { } } private void fireDeckSectionChangeListener(final int index, final DeckSection section, final CardPool cards) { - decks[index].putSection(section, cards); + final Deck copy = new Deck(decks[index]); + copy.putSection(section, cards); + decks[index] = copy; if (playerChangeListener != null) { playerChangeListener.update(index, UpdateLobbyPlayerEvent.deckUpdate(section, cards)); } @@ -486,7 +489,7 @@ public class VLobby implements IUpdateable { } private void selectPlanarDeck(final int playerIndex) { - if (playerIndex >= activePlayersNum) { + if (playerIndex >= activePlayersNum || !hasVariant(GameType.Planechase)) { return; } 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 35bf3517fba..a3a6c744af8 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 @@ -8,6 +8,7 @@ import javax.swing.JMenu; import forge.GuiBase; import forge.Singletons; import forge.UiCommand; +import forge.assets.FSkinProp; import forge.gui.FNetOverlay; import forge.gui.framework.FScreen; import forge.gui.framework.ICDoc; @@ -31,6 +32,7 @@ import forge.net.game.UpdateLobbyPlayerEvent; import forge.properties.ForgePreferences.FPref; import forge.screens.home.VLobby; import forge.screens.home.sanctioned.ConstructedGameMenu; +import forge.util.gui.SOptionPane; public enum CSubmenuOnlineLobby implements ICDoc, IMenuProvider { SINGLETON_INSTANCE; @@ -64,6 +66,9 @@ public enum CSubmenuOnlineLobby implements ICDoc, IMenuProvider { @Override public final void message(final String source, final String message) { FNetOverlay.SINGLETON_INSTANCE.addMessage(source, message); } + @Override public final void close() { + // NO-OP, server can't receive close message + } }); FNetOverlay.SINGLETON_INSTANCE.setGameClient(new IRemote() { @Override public final void send(final NetEvent event) { @@ -79,6 +84,7 @@ public enum CSubmenuOnlineLobby implements ICDoc, IMenuProvider { } }); + view.populate(); view.update(true); Singletons.getControl().setCurrentScreen(FScreen.ONLINE_LOBBY); @@ -88,6 +94,7 @@ public enum CSubmenuOnlineLobby implements ICDoc, IMenuProvider { final void join(final String hostname, final int port) { final IGuiGame gui = GuiBase.getInterface().getNewGuiGame(); final FGameClient client = new FGameClient(FModel.getPreferences().getPref(FPref.PLAYER_NAME), "0", gui); + VOnlineLobby.SINGLETON_INSTANCE.setClient(client); FNetOverlay.SINGLETON_INSTANCE.setGameClient(client); final ClientGameLobby lobby = new ClientGameLobby(); final VLobby view = VOnlineLobby.SINGLETON_INSTANCE.setLobby(lobby); @@ -100,6 +107,11 @@ public enum CSubmenuOnlineLobby implements ICDoc, IMenuProvider { lobby.setLocalPlayer(slot); lobby.setData(state); } + @Override public final void close() { + SOptionPane.showMessageDialog("Connection to the host was interrupted.", "Error", FSkinProp.ICO_WARNING); + VOnlineLobby.SINGLETON_INSTANCE.setClient(null); + FScreen.ONLINE_LOBBY.close(); + } }); view.setPlayerChangeListener(new IPlayerChangeListener() { @Override public final void update(final int index, final UpdateLobbyPlayerEvent event) { @@ -109,7 +121,7 @@ public enum CSubmenuOnlineLobby implements ICDoc, IMenuProvider { client.connect(hostname, port); Singletons.getControl().setCurrentScreen(FScreen.ONLINE_LOBBY); - FNetOverlay.SINGLETON_INSTANCE.showUp(String.format("Connected to %s:%s", hostname, port)); + FNetOverlay.SINGLETON_INSTANCE.showUp(String.format("Connected to %s:%d", hostname, port)); } @Override diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/online/VOnlineLobby.java b/forge-gui-desktop/src/main/java/forge/screens/home/online/VOnlineLobby.java index 3c43541b380..e476eb33e78 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/online/VOnlineLobby.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/online/VOnlineLobby.java @@ -6,6 +6,7 @@ import net.miginfocom.swing.MigLayout; import forge.deckchooser.DecksComboBoxEvent; import forge.deckchooser.FDeckChooser; import forge.deckchooser.IDecksComboBoxListener; +import forge.gui.FNetOverlay; import forge.gui.framework.DragCell; import forge.gui.framework.DragTab; import forge.gui.framework.EDocID; @@ -13,6 +14,8 @@ import forge.gui.framework.FScreen; import forge.gui.framework.IVDoc; import forge.gui.framework.IVTopLevelUI; import forge.match.GameLobby; +import forge.net.FGameClient; +import forge.net.FServerManager; import forge.screens.home.VLobby; import forge.toolbox.FPanel; import forge.util.gui.SOptionPane; @@ -26,6 +29,7 @@ public enum VOnlineLobby implements IVDoc, IVTopLevelUI { // General variables private VLobby lobby; + private FGameClient client; private VOnlineLobby() { } @@ -39,10 +43,15 @@ public enum VOnlineLobby implements IVDoc, IVTopLevelUI { return this.lobby; } + void setClient(final FGameClient client) { + this.client = client; + } + @Override public void populate() { final JPanel outerContainer = FView.SINGLETON_INSTANCE.getPnlInsets(); outerContainer.removeAll(); + final FPanel container = new FPanel(new MigLayout("insets 0, gap 0, wrap 1, ax right")); outerContainer.add(container); lobby.getLblTitle().setText("Online Multiplayer: Lobby"); @@ -106,7 +115,23 @@ public enum VOnlineLobby implements IVDoc, IVTopLevelUI { @Override public boolean onClosing(final FScreen screen) { - return SOptionPane.showConfirmDialog("Leave lobby?", "Leave"); + final FServerManager server = FServerManager.getInstance(); + if (server.isHosting()) { + if (SOptionPane.showConfirmDialog("Leave lobby? Doing so will shut down all connections and stop hosting.", "Leave")) { + FServerManager.getInstance().stopServer(); + return true; + } + } else { + if (client == null || SOptionPane.showConfirmDialog("Leave lobby?", "Leave")) { + if (client != null) { + client.close(); + client = null; + } + FNetOverlay.SINGLETON_INSTANCE.setGameClient(null); + return true; + } + } + return false; } } diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java b/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java index e4458c2ac82..f1d3481f3ec 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java @@ -104,6 +104,7 @@ import forge.toolbox.MouseTriggerEvent; import forge.toolbox.special.PhaseIndicator; import forge.toolbox.special.PhaseLabel; import forge.trackable.TrackableCollection; +import forge.util.FCollection; import forge.util.FCollectionView; import forge.util.ITriggerEvent; import forge.util.gui.SOptionPane; @@ -260,11 +261,6 @@ public final class CMatchUI return FSkin.getAvatars().get(avatarIdx >= 0 ? avatarIdx : defaultIndex); } - private void setAvatar(VField view, SkinImage img) { - view.getLblAvatar().setIcon(img); - view.getLblAvatar().getResizeTimer().start(); - } - private void initMatch(final FCollectionView sortedPlayers, final Iterable myPlayers) { this.sortedPlayers = sortedPlayers; this.setLocalPlayers(myPlayers); @@ -290,8 +286,7 @@ public final class CMatchUI commands.add(c); myDocs.put(commandDoc, c); - //setAvatar(f, new ImageIcon(FSkin.getAvatars().get())); - setAvatar(f, getPlayerAvatar(p, Integer.parseInt(indices[i > 2 ? 1 : 0]))); + f.setAvatar(getPlayerAvatar(p, Integer.parseInt(indices[i > 2 ? 1 : 0]))); f.getLayoutControl().initialize(); c.getLayoutControl().initialize(); i++; @@ -436,7 +431,7 @@ public final class CMatchUI if (vHand != null) { vHand.getLayoutControl().updateHand(); } - vf.getDetailsPanel().updateZones(); + vf.updateZones(); vf.updateDetails(); FloatingCardArea.refresh(owner, zt); break; @@ -448,7 +443,7 @@ public final class CMatchUI break; default: if (vf != null) { - vf.getDetailsPanel().updateZones(); + vf.updateZones(); } FloatingCardArea.refresh(owner, zt); break; @@ -460,7 +455,7 @@ public final class CMatchUI @Override public void updateManaPool(final Iterable manaPoolUpdate) { for (final PlayerView p : manaPoolUpdate) { - getFieldViewFor(p).getDetailsPanel().updateManaPool(); + getFieldViewFor(p).updateManaPool(); } } @@ -795,7 +790,12 @@ public final class CMatchUI final GameView gameView = getGameView(); gameView.getGameLog().addObserver(getCLog()); - initMatch(gameView.getPlayers(), myPlayers); + // Sort players + FCollectionView players = gameView.getPlayers(); + if (players.size() == 2 && myPlayers.size() == 1 && myPlayers.get(0).equals(players.get(1))) { + players = new FCollection(new PlayerView[] { players.get(1), players.get(0) }); + } + initMatch(players, myPlayers); actuateMatchPreferences(); diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/views/VField.java b/forge-gui-desktop/src/main/java/forge/screens/match/views/VField.java index 6cdeb8e085f..b1978f74c1d 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/views/VField.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/views/VField.java @@ -28,6 +28,7 @@ import javax.swing.border.Border; import javax.swing.border.LineBorder; import net.miginfocom.swing.MigLayout; +import forge.assets.FSkinProp; import forge.game.player.PlayerView; import forge.game.zone.ZoneType; import forge.gui.framework.DragCell; @@ -39,6 +40,7 @@ import forge.screens.match.controllers.CField; import forge.toolbox.FLabel; import forge.toolbox.FScrollPane; import forge.toolbox.FSkin; +import forge.toolbox.FSkin.SkinImage; import forge.toolbox.FSkin.SkinnedPanel; import forge.toolbox.special.PhaseIndicator; import forge.toolbox.special.PlayerDetailsPanel; @@ -50,6 +52,9 @@ import forge.view.arcane.PlayArea; *

(V at beginning of class name denotes a view class.) */ public class VField implements IVDoc { + private final static int LIFE_CRITICAL = 5; + private final static int POISON_CRITICAL = 8; + // Fields used with interface IVDoc private final CField control; private DragCell parentCell; @@ -57,7 +62,7 @@ public class VField implements IVDoc { private final DragTab tab = new DragTab("Field"); // Other fields - private PlayerView player = null; + private final PlayerView player; // Top-level containers private final FScrollPane scroller = new FScrollPane(false); @@ -68,7 +73,8 @@ public class VField implements IVDoc { // Avatar area private final FLabel lblAvatar = new FLabel.Builder().fontAlign(SwingConstants.CENTER).iconScaleFactor(1.0f).build(); - private final FLabel lblLife = new FLabel.Builder().fontAlign(SwingConstants.CENTER).fontStyle(Font.BOLD).build(); + private final FLabel lblLife = new FLabel.Builder().fontAlign(SwingConstants.CENTER).fontStyle(Font.BOLD).build(); + private final FLabel lblPoison = new FLabel.Builder().fontAlign(SwingConstants.CENTER).fontStyle(Font.BOLD).icon(FSkin.getImage(FSkinProp.IMG_ZONE_POISON)).iconInBackground().build(); private final PhaseIndicator phaseIndicator = new PhaseIndicator(); @@ -100,12 +106,13 @@ public class VField implements IVDoc { lblAvatar.setFocusable(false); lblLife.setFocusable(false); + lblPoison.setFocusable(false); avatarArea.setOpaque(false); avatarArea.setBackground(FSkin.getColor(FSkin.Colors.CLR_HOVER)); avatarArea.setLayout(new MigLayout("insets 0, gap 0")); avatarArea.add(lblAvatar, "w 100%-6px!, h 100%-23px!, wrap, gap 3 3 3 0"); - avatarArea.add(lblLife, "w 100%!, h 20px!"); + avatarArea.add(lblLife, "w 100%!, h 20px!, wrap"); // Player area hover effect avatarArea.addMouseListener(new MouseAdapter() { @@ -170,10 +177,6 @@ public class VField implements IVDoc { return this.parentCell; } - public PlayerView getPlayer() { - return this.player; - } - public PlayArea getTabletop() { return this.tabletop; } @@ -182,14 +185,6 @@ public class VField implements IVDoc { return this.avatarArea; } - public FLabel getLblAvatar() { - return this.lblAvatar; - } - - public FLabel getLblLife() { - return this.lblLife; - } - public PhaseIndicator getPhaseIndicator() { return phaseIndicator; } @@ -198,23 +193,67 @@ public class VField implements IVDoc { return detailsPanel; } - public boolean isHighlighted() { + private boolean isHighlighted() { return control.getMatchUI().isHighlighted(player); } + public void setAvatar(final SkinImage avatar) { + lblAvatar.setIcon(avatar); + lblAvatar.getResizeTimer().start(); + } + + public void updateManaPool() { + detailsPanel.updateManaPool(); + } + public void updateZones() { + detailsPanel.updateZones(); + } + + private void addLblPoison() { + if (lblPoison.isShowing()) { + return; + } + avatarArea.remove(lblLife); + avatarArea.add(lblLife, "w 50%!, h 20px!, split 2"); + avatarArea.add(lblPoison, "w 50%!, h 20px!, wrap"); + } + private void removeLblPoison() { + if (!lblPoison.isShowing()) { + return; + } + avatarArea.remove(lblPoison); + avatarArea.remove(lblLife); + avatarArea.add(lblLife, "w 100%!, h 20px!, wrap"); + } + public void updateDetails() { control.getMatchUI().getCPlayers().update(); detailsPanel.updateDetails(); - this.getLblLife().setText("" + player.getLife()); - if (player.getLife() > 5) { - this.getLblLife().setForeground(FSkin.getColor(FSkin.Colors.CLR_TEXT)); - } - else { - this.getLblLife().setForeground(Color.red); + // Update life total + final int life = player.getLife(); + lblLife.setText(String.valueOf(life)); + if (life > LIFE_CRITICAL) { + lblLife.setForeground(FSkin.getColor(FSkin.Colors.CLR_TEXT)); + } else { + lblLife.setForeground(Color.RED); } - boolean highlighted = isHighlighted(); + // Update poison counters + final int poison = player.getPoisonCounters(); + if (poison > 0) { + addLblPoison(); + lblPoison.setText(String.valueOf(poison)); + if (poison < POISON_CRITICAL) { + lblPoison.setForeground(FSkin.getColor(FSkin.Colors.CLR_TEXT)); + } else { + lblPoison.setForeground(Color.RED); + } + } else { + removeLblPoison(); + } + + final boolean highlighted = isHighlighted(); this.avatarArea.setBorder(highlighted ? borderAvatarHighlighted : borderAvatarSimple ); this.avatarArea.setOpaque(highlighted); this.avatarArea.setToolTipText(player.getDetailsHtml()); diff --git a/forge-gui-desktop/src/main/java/forge/toolbox/special/PlayerDetailsPanel.java b/forge-gui-desktop/src/main/java/forge/toolbox/special/PlayerDetailsPanel.java index 83aab0756f2..8eaeff0dbf5 100644 --- a/forge-gui-desktop/src/main/java/forge/toolbox/special/PlayerDetailsPanel.java +++ b/forge-gui-desktop/src/main/java/forge/toolbox/special/PlayerDetailsPanel.java @@ -1,6 +1,7 @@ package forge.toolbox.special; import java.awt.Color; +import java.awt.Component; import java.awt.Font; import java.awt.Graphics; import java.awt.event.MouseEvent; @@ -33,23 +34,23 @@ public class PlayerDetailsPanel extends JPanel { private final PlayerView player; // Info labels - private final FLabel lblHand = new DetailLabel(FSkinProp.IMG_ZONE_HAND, "99", "Cards in hand"); - private final FLabel lblGraveyard = new DetailLabel(FSkinProp.IMG_ZONE_GRAVEYARD, "99", "Cards in graveyard"); - private final FLabel lblLibrary = new DetailLabel(FSkinProp.IMG_ZONE_LIBRARY, "99", "Cards in library"); - private final FLabel lblExile = new DetailLabel(FSkinProp.IMG_ZONE_EXILE, "99", "Exiled cards"); - private final FLabel lblFlashback = new DetailLabel(FSkinProp.IMG_ZONE_FLASHBACK, "99", "Flashback cards"); - private final FLabel lblPoison = new DetailLabel(FSkinProp.IMG_ZONE_POISON, "99", "Poison counters"); + private final DetailLabel lblHand = new DetailLabel(FSkinProp.IMG_ZONE_HAND, "Hand (%s/%s)"); + private final DetailLabel lblGraveyard = new DetailLabel(FSkinProp.IMG_ZONE_GRAVEYARD, "Graveyard (%s)"); + private final DetailLabel lblLibrary = new DetailLabel(FSkinProp.IMG_ZONE_LIBRARY, "Library (%s)"); + private final DetailLabel lblExile = new DetailLabel(FSkinProp.IMG_ZONE_EXILE, "Exile (%s)"); + private final DetailLabel lblFlashback = new DetailLabel(FSkinProp.IMG_ZONE_FLASHBACK, "Flashback cards (%s)"); + private final DetailLabel lblPoison = new DetailLabel(FSkinProp.IMG_ZONE_POISON, "Poison counters (%s)"); private final List> manaLabels = new ArrayList>(); public PlayerDetailsPanel(final PlayerView player0) { player = player0; - manaLabels.add(Pair.of(new DetailLabel(FSkinProp.IMG_MANA_B, "99", "Black mana"), MagicColor.BLACK)); - manaLabels.add(Pair.of(new DetailLabel(FSkinProp.IMG_MANA_U, "99", "Blue mana"), MagicColor.BLUE)); - manaLabels.add(Pair.of(new DetailLabel(FSkinProp.IMG_MANA_G, "99", "Green mana"), MagicColor.GREEN)); - manaLabels.add(Pair.of(new DetailLabel(FSkinProp.IMG_MANA_R, "99", "Red mana"), MagicColor.RED)); - manaLabels.add(Pair.of(new DetailLabel(FSkinProp.IMG_MANA_W, "99", "White mana"), MagicColor.WHITE)); - manaLabels.add(Pair.of(new DetailLabel(FSkinProp.IMG_MANA_COLORLESS, "99", "Colorless mana"), (byte)0)); + manaLabels.add(Pair.of(new DetailLabel(FSkinProp.IMG_MANA_W, "White mana (%s)"), MagicColor.WHITE)); + manaLabels.add(Pair.of(new DetailLabel(FSkinProp.IMG_MANA_U, "Blue mana (%s)"), MagicColor.BLUE)); + manaLabels.add(Pair.of(new DetailLabel(FSkinProp.IMG_MANA_B, "Black mana (%s)"), MagicColor.BLACK)); + manaLabels.add(Pair.of(new DetailLabel(FSkinProp.IMG_MANA_R, "Red mana (%s)"), MagicColor.RED)); + manaLabels.add(Pair.of(new DetailLabel(FSkinProp.IMG_MANA_G, "Green mana (%s)"), MagicColor.GREEN)); + manaLabels.add(Pair.of(new DetailLabel(FSkinProp.IMG_MANA_COLORLESS, "Colorless mana (%s)"), MagicColor.COLORLESS)); setOpaque(false); setLayout(new MigLayout("insets 0, gap 0, wrap")); @@ -104,75 +105,60 @@ public class PlayerDetailsPanel extends JPanel { add(row5, constraintsRow); add(row6, constraintsRow); } - + + public Component getLblLibrary() { + return lblLibrary; + } + /** * Handles observer update of player Zones - hand, graveyard, etc. * * @param p0   {@link forge.game.player.Player} */ public void updateZones() { - getLblHand().setText("" + player.getHandSize()); - final String handMaxToolTip = player.hasUnlimitedHandSize() - ? "no maximum hand size" : String.valueOf(player.getMaxHandSize()); - getLblHand().setToolTipText("Cards in hand (max: " + handMaxToolTip + ")"); - getLblGraveyard().setText("" + player.getGraveyardSize()); - getLblLibrary().setText("" + player.getLibrarySize()); - getLblFlashback().setText("" + player.getFlashbackSize()); - getLblExile().setText("" + player.getExileSize()); + final String handSize = String.valueOf(player.getHandSize()), + graveyardSize = String.valueOf(player.getGraveyardSize()), + librarySize = String.valueOf(player.getLibrarySize()), + flashbackSize = String.valueOf(player.getFlashbackSize()), + exileSize = String.valueOf(player.getExileSize()); + lblHand.setText(handSize); + lblHand.setToolTip(handSize, player.getMaxHandString()); + lblGraveyard.setText(graveyardSize); + lblGraveyard.setToolTip(graveyardSize); + lblLibrary.setText(librarySize); + lblLibrary.setToolTip(librarySize); + lblFlashback.setText(flashbackSize); + lblFlashback.setToolTip(flashbackSize); + lblExile.setText(exileSize); + lblExile.setToolTip(exileSize); } /** - * Handles observer update of non-Zone details - life, poison, etc. Also - * updates "players" panel in tabber for this player. - * - * @param p0   {@link forge.game.player.Player} + * Handles observer update of non-Zone details (poison). */ public void updateDetails() { - // Poison/life - getLblPoison().setText("" + player.getPoisonCounters()); - if (player.getPoisonCounters() < 8) { - getLblPoison().setForeground(FSkin.getColor(FSkin.Colors.CLR_TEXT)); - } - else { - getLblPoison().setForeground(Color.red); + // Poison + final int poison = player.getPoisonCounters(); + lblPoison.setText(String.valueOf(poison)); + lblPoison.setToolTip(String.valueOf(poison)); + if (poison < 8) { + lblPoison.setForeground(FSkin.getColor(FSkin.Colors.CLR_TEXT)); + } else { + lblPoison.setForeground(Color.red); } } /** * Handles observer update of the mana pool. - * - * @param p0   {@link forge.game.player.Player} */ public void updateManaPool() { for (final Pair label : manaLabels) { - label.getKey().setText(Integer.toString(player.getMana(label.getRight()))); + final String mana = String.valueOf(player.getMana(label.getRight().byteValue())); + label.getKey().setText(mana); + label.getKey().setToolTip(mana); } } - public FLabel getLblHand() { - return lblHand; - } - - public FLabel getLblLibrary() { - return lblLibrary; - } - - public FLabel getLblGraveyard() { - return lblGraveyard; - } - - public FLabel getLblExile() { - return lblExile; - } - - public FLabel getLblFlashback() { - return lblFlashback; - } - - public FLabel getLblPoison() { - return lblPoison; - } - public void setupMouseActions(final ForgeAction handAction, final ForgeAction libraryAction, final ForgeAction exileAction, final ForgeAction graveAction, final ForgeAction flashBackAction, final Function manaAction) { // Detail label listeners @@ -212,7 +198,8 @@ public class PlayerDetailsPanel extends JPanel { @Override public void onLeftClick(final MouseEvent e) { //if shift key down, keep using mana until it runs out or no longer can be put towards the cost - while (manaAction.apply(labelPair.getRight()) && e.isShiftDown()) {} + final Byte mana = labelPair.getRight(); + while (manaAction.apply(mana) && e.isShiftDown()) {} } }); } @@ -220,33 +207,39 @@ public class PlayerDetailsPanel extends JPanel { @SuppressWarnings("serial") private class DetailLabel extends FLabel { - private DetailLabel(FSkinProp p0, String s0, String s1) { - super(new FLabel.Builder().icon(FSkin.getImage(p0)) + private final String tooltip; + private DetailLabel(final FSkinProp icon, final String tooltip) { + super(new FLabel.Builder().icon(FSkin.getImage(icon)) .opaque(false).fontSize(14).hoverable() .fontStyle(Font.BOLD).iconInBackground() - .text(s0).tooltip(s1).fontAlign(SwingConstants.RIGHT)); + .fontAlign(SwingConstants.RIGHT)); + this.tooltip = tooltip; setFocusable(false); } - public void setText(String text0) { + public void setText(final String text0) { super.setText(text0); autoSizeFont(); } + public void setToolTip(final String... args) { + super.setToolTipText(String.format(tooltip, (Object[]) args)); + } + protected void resetIcon() { super.resetIcon(); autoSizeFont(); } private void autoSizeFont() { - String text = getText(); + final String text = getText(); if (StringUtils.isEmpty(text)) { return; } - Graphics g = getGraphics(); + final Graphics g = getGraphics(); if (g == null) { return; } - int max = getMaxTextWidth(); + final int max = getMaxTextWidth(); SkinFont font = null; for (int fontSize = 14; fontSize > 5; fontSize--) { diff --git a/forge-gui/src/main/java/forge/interfaces/ILobbyListener.java b/forge-gui/src/main/java/forge/interfaces/ILobbyListener.java index 08aa1dad4f3..74aab55bf9a 100644 --- a/forge-gui/src/main/java/forge/interfaces/ILobbyListener.java +++ b/forge-gui/src/main/java/forge/interfaces/ILobbyListener.java @@ -5,4 +5,5 @@ import forge.match.GameLobby.GameLobbyData; public interface ILobbyListener { void message(String source, String message); void update(GameLobbyData state, int slot); + void close(); } diff --git a/forge-gui/src/main/java/forge/match/GameLobby.java b/forge-gui/src/main/java/forge/match/GameLobby.java index 5daefd93149..011d68a12e2 100644 --- a/forge-gui/src/main/java/forge/match/GameLobby.java +++ b/forge-gui/src/main/java/forge/match/GameLobby.java @@ -41,6 +41,7 @@ public abstract class GameLobby { private final static int MAX_PLAYERS = 8; private GameLobbyData data = new GameLobbyData(); + private GameType currentGameType = GameType.Constructed; private int lastArchenemy = 0; private IUpdateable listener; @@ -69,10 +70,10 @@ public abstract class GameLobby { } public GameType getGameType() { - return data.currentGameMode; + return currentGameType; } public void setGameType(final GameType type) { - data.currentGameMode = type; + currentGameType = type; } public boolean hasVariant(final GameType variant) { @@ -90,7 +91,7 @@ public abstract class GameLobby { } public void applyToSlot(final int index, final UpdateLobbyPlayerEvent event) { final LobbySlot slot = getSlot(index); - if (slot == null) { + if (slot == null || event == null) { throw new NullPointerException(); } @@ -130,10 +131,10 @@ public abstract class GameLobby { } public abstract boolean hasControl(); - public abstract boolean mayEdit(final int index); - public abstract boolean mayControl(final int index); - public abstract boolean mayRemove(final int index); - protected abstract IGuiGame getGui(final int index); + public abstract boolean mayEdit(int index); + public abstract boolean mayControl(int index); + public abstract boolean mayRemove(int index); + protected abstract IGuiGame getGui(int index); protected abstract void onGameStarted(); public void addSlot() { @@ -142,6 +143,9 @@ public abstract class GameLobby { addSlot(new LobbySlot(type, null, newIndex, newIndex, false, !allowNetworking, Collections.emptySet())); } protected final void addSlot(final LobbySlot slot) { + if (slot == null) { + throw new NullPointerException(); + } if (data.slots.size() >= MAX_PLAYERS) { return; } @@ -197,7 +201,7 @@ public abstract class GameLobby { } public void applyVariant(final GameType variant) { - data.currentGameMode = variant; + setGameType(variant); data.appliedVariants.add(variant); //ensure other necessary variants are unchecked @@ -231,17 +235,21 @@ public abstract class GameLobby { } public void removeVariant(final GameType variant) { - data.appliedVariants.remove(variant); + final boolean changed = data.appliedVariants.remove(variant); + if (!changed) { + return; + } + if (data.appliedVariants.isEmpty()) { data.appliedVariants.add(GameType.Constructed); } - if (variant == data.currentGameMode) { + if (variant == currentGameType) { if (hasVariant(GameType.Commander)) { - data.currentGameMode = GameType.Commander; + currentGameType = GameType.Commander; } else if (hasVariant(GameType.TinyLeaders)) { - data.currentGameMode = GameType.TinyLeaders; + currentGameType = GameType.TinyLeaders; } else { - data.currentGameMode = GameType.Constructed; + currentGameType = GameType.Constructed; } } updateView(true); @@ -275,7 +283,7 @@ public abstract class GameLobby { } final List activeSlots = Lists.newArrayListWithCapacity(getNumberOfSlots()); - for (final LobbySlot slot : data.getSlots()) { + for (final LobbySlot slot : data.slots) { if (slot.getType() != LobbySlotType.OPEN) { activeSlots.add(slot); } @@ -290,6 +298,12 @@ public abstract class GameLobby { SOptionPane.showMessageDialog(String.format("Please specify a deck for %s", slot.getName())); return; } + if (hasVariant(GameType.Commander) || hasVariant(GameType.TinyLeaders)) { + if (!slot.getDeck().has(DeckSection.Commander)) { + SOptionPane.showMessageDialog(String.format("%s doesn't have a commander", slot.getName())); + return; + } + } } final Set variantTypes = data.appliedVariants; @@ -391,7 +405,7 @@ public abstract class GameLobby { return; } } - schemes = schemePool.toFlatList(); + schemes = schemePool == null ? Collections.emptyList() : schemePool.toFlatList(); } //Planechase @@ -404,7 +418,7 @@ public abstract class GameLobby { return; } } - planes = planePool.toFlatList(); + planes = planePool == null ? Collections.emptyList() : planePool.toFlatList(); } //Vanguard @@ -441,23 +455,12 @@ public abstract class GameLobby { } public final static class GameLobbyData implements Serializable { - private static final long serialVersionUID = -9038854736144187540L; + private static final long serialVersionUID = 9184758307999646864L; - private transient GameType currentGameMode = GameType.Constructed; private final Set appliedVariants = EnumSet.of(GameType.Constructed); private final List slots = Lists.newArrayList(); - public GameType getCurrentGameMode() { - return currentGameMode; - } - public void setCurrentGameMode(GameType currentGameMode) { - this.currentGameMode = currentGameMode; - } - public Set getAppliedVariants() { - return Collections.unmodifiableSet(appliedVariants); - } - public List getSlots() { - return Collections.unmodifiableList(slots); + public GameLobbyData() { } } } diff --git a/forge-gui/src/main/java/forge/match/LobbySlot.java b/forge-gui/src/main/java/forge/match/LobbySlot.java index c3707d6ae26..055095fce95 100644 --- a/forge-gui/src/main/java/forge/match/LobbySlot.java +++ b/forge-gui/src/main/java/forge/match/LobbySlot.java @@ -62,10 +62,11 @@ public final class LobbySlot implements Serializable { setAiOptions(data.getAiOptions()); changed = true; } + final Deck oldDeck = getDeck(); if (data.getDeck() != null) { setDeck(data.getDeck()); - } else if (getDeck() != null && data.getSection() != null && data.getCards() != null) { - getDeck().putSection(data.getSection(), data.getCards()); + } else if (oldDeck != null && data.getSection() != null && data.getCards() != null) { + oldDeck.putSection(data.getSection(), data.getCards()); } return changed; } diff --git a/forge-gui/src/main/java/forge/net/FGameClient.java b/forge-gui/src/main/java/forge/net/FGameClient.java index 64879befe3b..2c1a4f43a64 100644 --- a/forge-gui/src/main/java/forge/net/FGameClient.java +++ b/forge-gui/src/main/java/forge/net/FGameClient.java @@ -104,6 +104,10 @@ public class FGameClient implements IToServer { } } + public void close() { + channel.close(); + } + @Override public void send(final NetEvent event) { System.out.println("Client sent " + event); @@ -382,5 +386,13 @@ public class FGameClient implements IToServer { } super.channelRead(ctx, msg); } + + @Override + public void channelInactive(final ChannelHandlerContext ctx) throws Exception { + for (final ILobbyListener listener : lobbyListeners) { + listener.close(); + } + super.channelInactive(ctx); + } } } diff --git a/forge-gui/src/main/java/forge/net/FServerManager.java b/forge-gui/src/main/java/forge/net/FServerManager.java index 4328f301df3..c8dab788713 100644 --- a/forge-gui/src/main/java/forge/net/FServerManager.java +++ b/forge-gui/src/main/java/forge/net/FServerManager.java @@ -52,6 +52,7 @@ import com.google.common.collect.Maps; public final class FServerManager { private static FServerManager instance = null; + private boolean isHosting = false; private final EventLoopGroup bossGroup = new NioEventLoopGroup(1); private final EventLoopGroup workerGroup = new NioEventLoopGroup(); private final Map clients = Maps.newTreeMap(); @@ -102,6 +103,7 @@ public final class FServerManager { } }).start(); + isHosting = true; } catch (final InterruptedException e) { e.printStackTrace(); } @@ -110,6 +112,11 @@ public final class FServerManager { public void stopServer() { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); + isHosting = false; + } + + public boolean isHosting() { + return isHosting; } public void broadcast(final NetEvent event) {