diff --git a/.gitattributes b/.gitattributes index cd2b4ed36f5..7a5135aabdb 100644 --- a/.gitattributes +++ b/.gitattributes @@ -17392,7 +17392,9 @@ forge-gui/src/main/java/forge/interfaces/IGameController.java -text forge-gui/src/main/java/forge/interfaces/IGuiBase.java -text forge-gui/src/main/java/forge/interfaces/IGuiGame.java -text forge-gui/src/main/java/forge/interfaces/IGuiTimer.java -text +forge-gui/src/main/java/forge/interfaces/ILobby.java -text forge-gui/src/main/java/forge/interfaces/IMayViewCards.java -text +forge-gui/src/main/java/forge/interfaces/IPlayerChangeListener.java -text forge-gui/src/main/java/forge/interfaces/IProgressBar.java -text forge-gui/src/main/java/forge/interfaces/ITextField.java -text forge-gui/src/main/java/forge/interfaces/IWinLoseView.java -text @@ -17610,6 +17612,9 @@ forge-net/src/main/java/forge/net/client/state/UnauthorizedClientState.java -tex forge-net/src/main/java/forge/net/client/state/package-info.java -text forge-net/src/main/java/forge/net/game/GuiGameEvent.java -text forge-net/src/main/java/forge/net/game/IRemote.java -text +forge-net/src/main/java/forge/net/game/LobbySlotType.java -text +forge-net/src/main/java/forge/net/game/LobbyState.java -text +forge-net/src/main/java/forge/net/game/LobbyUpdateEvent.java -text forge-net/src/main/java/forge/net/game/LoginEvent.java -text forge-net/src/main/java/forge/net/game/LogoutEvent.java -text forge-net/src/main/java/forge/net/game/MessageEvent.java -text diff --git a/forge-gui-desktop/src/main/java/forge/gui/FNetOverlay.java b/forge-gui-desktop/src/main/java/forge/gui/FNetOverlay.java index 388e50028d3..09398d3e90f 100644 --- a/forge-gui-desktop/src/main/java/forge/gui/FNetOverlay.java +++ b/forge-gui-desktop/src/main/java/forge/gui/FNetOverlay.java @@ -1,23 +1,30 @@ package forge.gui; -import forge.model.FModel; -import forge.net.FGameClient; -import forge.net.game.MessageEvent; -import forge.properties.ForgePreferences.FPref; -import forge.toolbox.*; -import forge.toolbox.FSkin.SkinnedPanel; -import net.miginfocom.swing.MigLayout; - -import org.apache.commons.lang3.StringUtils; - -import javax.swing.*; - -import java.awt.*; +import java.awt.Graphics; +import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.text.SimpleDateFormat; import java.util.Date; +import javax.swing.ScrollPaneConstants; + +import net.miginfocom.swing.MigLayout; + +import org.apache.commons.lang3.StringUtils; + +import forge.model.FModel; +import forge.net.game.IRemote; +import forge.net.game.MessageEvent; +import forge.properties.ForgePreferences.FPref; +import forge.toolbox.FLabel; +import forge.toolbox.FScrollPane; +import forge.toolbox.FSkin; +import forge.toolbox.FSkin.SkinnedPanel; +import forge.toolbox.FTextArea; +import forge.toolbox.FTextField; +import forge.toolbox.SmartScroller; + /** * TODO: Write javadoc for this type. * @@ -39,9 +46,9 @@ public enum FNetOverlay { private int height = 120; private int width = 400; - private FGameClient client = null; - public void setGameClient(final FGameClient client) { - this.client = client; + private IRemote remote = null; + public void setGameClient(final IRemote remote) { + this.remote = remote; } private final ActionListener onSend = new ActionListener() { @@ -52,8 +59,8 @@ public enum FNetOverlay { return; } - if (client != null) { - client.send(new MessageEvent(FModel.getPreferences().getPref(FPref.PLAYER_NAME), message)); + if (remote != null) { + remote.send(new MessageEvent(FModel.getPreferences().getPref(FPref.PLAYER_NAME), message)); } // lobby.speak(ChatArea.Room, lobby.getGuiPlayer(), message); } diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/PlayerPanel.java b/forge-gui-desktop/src/main/java/forge/screens/home/PlayerPanel.java index c3a7f0ae9d0..57929774540 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/PlayerPanel.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/PlayerPanel.java @@ -26,11 +26,13 @@ import forge.game.GameType; import forge.gui.framework.FScreen; import forge.item.PaperCard; import forge.model.FModel; +import forge.net.game.LobbySlotType; import forge.properties.ForgePreferences; import forge.properties.ForgePreferences.FPref; import forge.screens.deckeditor.CDeckEditorUI; import forge.screens.deckeditor.controllers.CEditorCommander; import forge.screens.deckeditor.controllers.CEditorVariant; +import forge.screens.home.VLobby.LobbyType; import forge.screens.home.sanctioned.AvatarSelector; import forge.toolbox.FComboBox; import forge.toolbox.FComboBoxWrapper; @@ -52,7 +54,10 @@ public class PlayerPanel extends FPanel { private static final SkinColor unfocusedPlayerOverlay = FSkin.getColor(FSkin.Colors.CLR_OVERLAY).alphaColor(120); private final int index; - private final boolean allowRemote; + private final LobbyType lobbyType; + + private LobbySlotType type = LobbySlotType.LOCAL; + private boolean editableForClient; private final FLabel nameRandomiser; private final FLabel avatarLabel = new FLabel.Builder().opaque(true).hoverable(true).iconScaleFactor(0.99f).iconInBackground(true).build(); @@ -63,12 +68,11 @@ public class PlayerPanel extends FPanel { private FRadioButton radioAi; private JCheckBoxMenuItem radioAiUseSimulation; private FRadioButton radioOpen; - /** Whether this panel is occupied by a remote player. */ - private boolean isRemote; private FComboBoxWrapper teamComboBox = new FComboBoxWrapper(); private FComboBoxWrapper aeTeamComboBox = new FComboBoxWrapper(); + private final FLabel closeBtn; private final FLabel deckBtn = new FLabel.ButtonBuilder().text("Select a deck").build(); private final FLabel deckLabel; @@ -91,7 +95,7 @@ public class PlayerPanel extends FPanel { private final FLabel vgdLabel; private final VLobby lobby; - public PlayerPanel(final VLobby lobby, final int index, final boolean allowRemote) { + public PlayerPanel(final VLobby lobby, final int index, final LobbyType lobbyType) { super(); this.lobby = lobby; this.deckLabel = lobby.newLabel("Deck:"); @@ -101,14 +105,14 @@ public class PlayerPanel extends FPanel { this.vgdLabel = lobby.newLabel("Vanguard:"); this.index = index; - this.allowRemote = allowRemote; + this.lobbyType = lobbyType; this.playerIsArchenemy = index == 0; setLayout(new MigLayout("insets 10px, gap 5px")); // Add a button to players 3+ (or if server) to remove them from the setup - if (index >= 2 || allowRemote) { - FLabel closeBtn = createCloseButton(); + closeBtn = createCloseButton(); + if (index >= 2 || lobbyType == LobbyType.SERVER) { this.add(closeBtn, "w 20, h 20, pos (container.w-20) 0"); } @@ -132,7 +136,7 @@ public class PlayerPanel extends FPanel { aeTeamComboBox.addActionListener(teamListener); teamComboBox.addTo(this, variantBtnConstraints + ", pushx, growx, gaptop 5px"); aeTeamComboBox.addTo(this, variantBtnConstraints + ", pushx, growx, gaptop 5px"); - if (allowRemote) { + if (lobbyType == LobbyType.SERVER) { this.add(radioOpen, "gapleft 1px"); } @@ -168,13 +172,31 @@ public class PlayerPanel extends FPanel { update(); } - private void update() { - final boolean enableComponents = !(isOpen() || isRemote()); - avatarLabel.setEnabled(enableComponents); - txtPlayerName.setEnabled(enableComponents); - nameRandomiser.setEnabled(enableComponents); - deckLabel.setVisible(enableComponents); - deckBtn.setVisible(enableComponents); + void update() { + if (type != LobbySlotType.REMOTE) { + if (radioHuman.isSelected()) { + type = LobbySlotType.LOCAL; + } else if (radioAi.isSelected()) { + type = LobbySlotType.AI; + } else if (radioOpen.isSelected()) { + type = LobbySlotType.OPEN; + } + } + + final boolean isEditable = lobbyType == LobbyType.LOCAL || type == LobbySlotType.LOCAL || + (lobbyType == LobbyType.SERVER && (type == LobbySlotType.LOCAL || type == LobbySlotType.AI)) || + (lobbyType == LobbyType.CLIENT && editableForClient); + avatarLabel.setEnabled(isEditable); + txtPlayerName.setEnabled(isEditable); + nameRandomiser.setEnabled(isEditable); + deckLabel.setVisible(isEditable); + deckBtn.setVisible(isEditable); + + final boolean hasSlotControls = lobbyType == LobbyType.LOCAL || (lobbyType == LobbyType.SERVER && type != LobbySlotType.REMOTE); + closeBtn.setVisible(hasSlotControls); + radioAi.setVisible(hasSlotControls); + radioHuman.setVisible(hasSlotControls); + radioOpen.setVisible(hasSlotControls && lobbyType == LobbyType.SERVER); } private final FMouseAdapter radioMouseAdapter = new FMouseAdapter() { @@ -204,6 +226,7 @@ public class PlayerPanel extends FPanel { prefs.setPref(FPref.PLAYER_NAME, newName); prefs.save(); } + lobby.firePlayerChangeListener(); } } }; @@ -244,6 +267,8 @@ public class PlayerPanel extends FPanel { if (index < 2) { PlayerPanel.this.lobby.updateAvatarPrefs(); } + + lobby.firePlayerChangeListener(); } @Override public final void onRightClick(final MouseEvent e) { if (!avatarLabel.isEnabled()) { @@ -334,29 +359,38 @@ public class PlayerPanel extends FPanel { return index; } + LobbySlotType getType() { + return type; + } + public boolean isAi() { - return radioAi.isSelected(); + return type == LobbySlotType.AI; } public boolean isSimulatedAi() { return radioAi.isSelected() && radioAiUseSimulation.isSelected(); } - public boolean isOpen() { - return radioOpen.isSelected() && !isRemote; + public boolean isLocal() { + return type == LobbySlotType.LOCAL; } public boolean isArchenemy() { return playerIsArchenemy; } - public boolean isRemote() { - return isRemote; + public void setRemote(final boolean remote) { + if (remote) { + type = LobbySlotType.REMOTE; + } else { + radioOpen.setSelected(true); + type = LobbySlotType.OPEN; + } + update(); } - public void setRemote(final boolean remote) { - isRemote = remote; - update(); + public void setEditableForClient(final boolean editable) { + editableForClient = editable; } public void setVanguardButtonText(String text) { @@ -516,9 +550,10 @@ public class PlayerPanel extends FPanel { * @param index */ private void createPlayerTypeOptions() { - radioHuman = new FRadioButton(allowRemote ? "Local" : "Human", index == 0); - radioAi = new FRadioButton("AI", !allowRemote && index != 0); - radioOpen = new FRadioButton("Open", allowRemote && index != 0); + final boolean isServer = lobbyType == LobbyType.SERVER; + radioHuman = new FRadioButton(isServer ? "Local" : "Human", index == 0); + radioAi = new FRadioButton("AI", !isServer && index != 0); + radioOpen = new FRadioButton("Open", isServer && index != 0); final JPopupMenu menu = new JPopupMenu(); radioAiUseSimulation = new JCheckBoxMenuItem("Use Simulation"); menu.add(radioAiUseSimulation); @@ -605,7 +640,7 @@ public class PlayerPanel extends FPanel { .icon(FSkin.getIcon(FSkinProp.ICO_CLOSE)).hoverable(true).build(); closeBtn.setCommand(new Runnable() { @Override public final void run() { - if (isRemote() && !SOptionPane.showConfirmDialog("Really kick player?", "Kick", false)) { + if (type == LobbySlotType.REMOTE && !SOptionPane.showConfirmDialog(String.format("Really kick %s?", getPlayerName()), "Kick", false)) { return; } PlayerPanel.this.lobby.removePlayer(index); @@ -639,6 +674,8 @@ public class PlayerPanel extends FPanel { random = MyRandom.getRandom().nextInt(FSkin.getAvatars().size()); } while (usedAvatars.contains(random)); setAvatar(random); + + lobby.firePlayerChangeListener(); } public void setAvatar(int newAvatarIndex) { diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/VHomeUI.java b/forge-gui-desktop/src/main/java/forge/screens/home/VHomeUI.java index 5e83e8300fa..219cf8333e4 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/VHomeUI.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/VHomeUI.java @@ -137,7 +137,7 @@ public enum VHomeUI implements IVTopLevelUI { allSubmenus.add(VSubmenuSealed.SINGLETON_INSTANCE); //allSubmenus.add(VSubmenuWinston.SINGLETON_INSTANCE); - //allSubmenus.add(VSubmenuOnlineLobby.SINGLETON_INSTANCE); + allSubmenus.add(VSubmenuOnlineLobby.SINGLETON_INSTANCE); allSubmenus.add(VSubmenuDuels.SINGLETON_INSTANCE); allSubmenus.add(VSubmenuChallenges.SINGLETON_INSTANCE); 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 fdaf0b2c88a..531c312a727 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 @@ -35,8 +35,14 @@ import forge.deckchooser.IDecksComboBoxListener; import forge.game.GameType; import forge.game.card.CardView; import forge.gui.CardDetailPanel; +import forge.interfaces.ILobby; +import forge.interfaces.IPlayerChangeListener; import forge.item.PaperCard; import forge.model.FModel; +import forge.net.game.LobbySlotType; +import forge.net.game.LobbyState; +import forge.net.game.LobbyState.LobbyPlayerData; +import forge.net.game.server.RemoteClient; import forge.properties.ForgePreferences; import forge.properties.ForgePreferences.FPref; import forge.toolbox.FCheckBox; @@ -57,13 +63,17 @@ import forge.util.NameGenerator; * *

(V at beginning of class name denotes a view class.) */ -public class VLobby { +public class VLobby implements ILobby { static final int MAX_PLAYERS = 8; private static final ForgePreferences prefs = FModel.getPreferences(); + public enum LobbyType { LOCAL, SERVER, CLIENT; } + // General variables - private final boolean allowRemote; + private final LobbyType type; + private int localPlayer = 0; + private IPlayerChangeListener playerChangeListener = null; private final LblHeader lblTitle = new LblHeader("Sanctioned Format: Constructed"); private int activePlayersNum = 2; private int playerWithFocus = 0; // index of the player that currently has focus @@ -122,8 +132,9 @@ public class VLobby { private final Vector aiListData = new Vector(); // CTR - public VLobby(final boolean allowRemote) { - this.allowRemote = allowRemote; + public VLobby(final LobbyType type) { + this.type = type; + lblTitle.setBackground(FSkin.getColor(FSkin.Colors.CLR_THEME2)); //////////////////////////////////////////////////////// @@ -153,7 +164,10 @@ public class VLobby { teams.add(i + 1); archenemyTeams.add(i == 0 ? 1 : 2); - final PlayerPanel player = new PlayerPanel(this, i, allowRemote); + final PlayerPanel player = new PlayerPanel(this, i, type); + if (type == LobbyType.CLIENT) { + player.setRemote(true); + } playerPanels.add(player); // Populate players panel @@ -172,14 +186,15 @@ public class VLobby { playersFrame.setOpaque(false); playersFrame.add(playersScroll, "w 100%, h 100%-35px"); - addPlayerBtn.setFocusable(true); - addPlayerBtn.setCommand(new Runnable() { - @Override - public void run() { - addPlayer(); - } - }); - playersFrame.add(addPlayerBtn, "height 30px!, growx, pushx"); + if (type != LobbyType.CLIENT) { + addPlayerBtn.setFocusable(true); + addPlayerBtn.setCommand(new Runnable() { + @Override public final void run() { + addPlayer(); + } + }); + playersFrame.add(addPlayerBtn, "height 30px!, growx, pushx"); + } constructedFrame.add(playersFrame, "gapright 10px, w 50%-5px, growy, pushy"); @@ -194,8 +209,10 @@ public class VLobby { decksFrame.setOpaque(false); // Start Button - pnlStart.setOpaque(false); - pnlStart.add(btnStart, "align center"); + if (type != LobbyType.CLIENT) { + pnlStart.setOpaque(false); + pnlStart.add(btnStart, "align center"); + } } public void populate() { @@ -213,20 +230,23 @@ public class VLobby { } - public void addPlayerInFreeSlot(final String name) { + private int addPlayerInFreeSlot(final String name) { if (activePlayersNum >= MAX_PLAYERS) { - return; + return -1; } for (final PlayerPanel pp : getPlayerPanels()) { - if (pp.isVisible() && pp.isOpen()) { - addPlayer(pp.getIndex()); + if (pp.isVisible() && ( + pp.getType() == LobbySlotType.OPEN || (pp.isLocal() && type == LobbyType.SERVER))) { + final int index = pp.getIndex(); + addPlayer(index); pp.setPlayerName(name); - pp.setRemote(true); + System.out.println("Put player " + name + " in slot " + index); - return; + return index; } } + return -1; } private void addPlayer() { if (activePlayersNum >= MAX_PLAYERS) { @@ -250,10 +270,12 @@ public class VLobby { playerPanels.get(slot).setVisible(true); playerPanels.get(slot).focusOnAvatar(); + + firePlayerChangeListener(); } void removePlayer(final int playerIndex) { - if (activePlayersNum < playerIndex) { + if (activePlayersNum <= playerIndex) { return; } activePlayersNum--; @@ -279,6 +301,69 @@ public class VLobby { changePlayerFocus(closest); playerPanels.get(closest).focusOnAvatar(); } + firePlayerChangeListener(); + } + + @Override + public int login(final RemoteClient client) { + return addPlayerInFreeSlot(client.getUsername()); + } + + @Override + public void logout(final RemoteClient client) { + removePlayer(client.getIndex()); + } + + @Override + public LobbyState getState() { + final LobbyState state = new LobbyState(); + for (int i = 0; i < activePlayersNum; i++) { + state.addPlayer(getData(i)); + } + return state; + } + + public void setState(final LobbyState state) { + setLocalPlayer(state.getLocalPlayer()); + + final List players = state.getPlayers(); + final int pSize = players.size(); + activePlayersNum = pSize; + for (int i = 0; i < pSize; i++) { + final LobbyPlayerData player = players.get(i); + final PlayerPanel panel = playerPanels.get(i); + + if (type == LobbyType.CLIENT) { + panel.setRemote(i != localPlayer); + panel.setEditableForClient(i == localPlayer); + } else { + panel.setRemote(player.getType() == LobbySlotType.REMOTE); + panel.setEditableForClient(false); + } + panel.setPlayerName(player.getName()); + panel.setAvatar(player.getAvatarIndex()); + panel.setVisible(true); + panel.update(); + } + } + + private void setLocalPlayer(final int index) { + localPlayer = index; + } + + public void setPlayerChangeListener(final IPlayerChangeListener listener) { + this.playerChangeListener = listener; + } + + void firePlayerChangeListener() { + if (playerChangeListener != null) { + playerChangeListener.update(getData(localPlayer)); + } + } + + private LobbyPlayerData getData(final int index) { + final PlayerPanel panel = playerPanels.get(index); + return new LobbyPlayerData(panel.getPlayerName(), panel.getAvatarIndex(), panel.getType()); } /** Builds the actual deck panel layouts for each player. @@ -373,7 +458,7 @@ public class VLobby { private void populateDeckPanel(final GameType forGameType) { decksFrame.removeAll(); - if (playerPanelWithFocus.isOpen() || playerPanelWithFocus.isRemote()) { + if (playerPanelWithFocus.getType() == LobbySlotType.OPEN || playerPanelWithFocus.getType() == LobbySlotType.REMOTE) { return; } @@ -439,8 +524,8 @@ public class VLobby { return deckChoosers.get(playernum); } - public List getTeams() { return Collections.unmodifiableList(teams); } - public List getArchenemyTeams() { return Collections.unmodifiableList(archenemyTeams); } + public List getTeams() { return teams; } + public List getArchenemyTeams() { return archenemyTeams; } public GameType getCurrentGameMode() { return currentGameMode; } public void setCurrentGameMode(final GameType mode) { currentGameMode = mode; } diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/online/COnlineLobby.java b/forge-gui-desktop/src/main/java/forge/screens/home/online/COnlineLobby.java index 9e6c2f0fa11..dd10d014987 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/online/COnlineLobby.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/online/COnlineLobby.java @@ -3,12 +3,17 @@ package forge.screens.home.online; import forge.UiCommand; import forge.gui.framework.ICDoc; import forge.screens.home.CLobby; +import forge.screens.home.VLobby; public enum COnlineLobby implements ICDoc { SINGLETON_INSTANCE; - private final VOnlineLobby view = VOnlineLobby.SINGLETON_INSTANCE; - private final CLobby lobby = new CLobby(view.getLobby()); + private CLobby lobby; + + void setLobby(VLobby lobbyView) { + lobby = new CLobby(lobbyView); + initialize(); + } @Override public void register() { @@ -19,7 +24,9 @@ public enum COnlineLobby implements ICDoc { */ @Override public void update() { - lobby.update(); + if (lobby != null) { + lobby.update(); + } } /* (non-Javadoc) @@ -27,7 +34,9 @@ public enum COnlineLobby implements ICDoc { */ @Override public void initialize() { - lobby.initialize(); + if (lobby != null) { + lobby.initialize(); + } } /* (non-Javadoc) 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 03dc85c2ca7..63461ba09ee 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 @@ -13,40 +13,71 @@ import forge.game.GameType; import forge.gui.FNetOverlay; import forge.gui.framework.FScreen; import forge.gui.framework.ICDoc; +import forge.interfaces.IPlayerChangeListener; import forge.menus.IMenuProvider; import forge.menus.MenuUtil; import forge.model.FModel; import forge.net.FGameClient; import forge.net.FServerManager; +import forge.net.game.LobbyState; +import forge.net.game.LobbyState.LobbyPlayerData; +import forge.net.game.LoginEvent; import forge.net.game.client.ILobbyListener; -import forge.net.game.server.RemoteClient; import forge.properties.ForgePreferences.FPref; +import forge.screens.home.VLobby; +import forge.screens.home.VLobby.LobbyType; import forge.screens.home.sanctioned.ConstructedGameMenu; public enum CSubmenuOnlineLobby implements ICDoc, IMenuProvider { SINGLETON_INSTANCE; final void host(final int portNumber) { + final VLobby lobby = VOnlineLobby.SINGLETON_INSTANCE.setLobby(LobbyType.SERVER); + FServerManager.getInstance().startServer(portNumber); - FServerManager.getInstance().registerLobbyListener(new ILobbyListener() { - @Override public final void logout(final RemoteClient client) { + FServerManager.getInstance().setLobby(lobby); + FServerManager.getInstance().hostGame(new GameRules(GameType.Constructed)); + + FNetOverlay.SINGLETON_INSTANCE.showUp("Hosting game"); + lobby.setPlayerChangeListener(new IPlayerChangeListener() { + @Override public final void update(final LobbyPlayerData data) { + FServerManager.getInstance().updateLobbyState(); } - @Override public final void login(final RemoteClient client) { - VOnlineLobby.SINGLETON_INSTANCE.getLobby().addPlayerInFreeSlot(client.getUsername()); + }); + + final FGameClient client = new FGameClient(FModel.getPreferences().getPref(FPref.PLAYER_NAME), "0", GuiBase.getInterface().getNewGuiGame()); + FNetOverlay.SINGLETON_INSTANCE.setGameClient(client); + client.addLobbyListener(new ILobbyListener() { + @Override public final void update(final LobbyState state) { + lobby.setState(state); } @Override public final void message(final String source, final String message) { FNetOverlay.SINGLETON_INSTANCE.addMessage(source, message); } }); - FServerManager.getInstance().hostGame(new GameRules(GameType.Constructed)); + client.connect("localhost", portNumber); Singletons.getControl().setCurrentScreen(FScreen.ONLINE_LOBBY); - FNetOverlay.SINGLETON_INSTANCE.showUp("Hosting game"); + FNetOverlay.SINGLETON_INSTANCE.showUp(String.format("Hosting on port %d", portNumber)); } final void join(final String hostname, final int port) { final FGameClient client = new FGameClient(FModel.getPreferences().getPref(FPref.PLAYER_NAME), "0", GuiBase.getInterface().getNewGuiGame()); FNetOverlay.SINGLETON_INSTANCE.setGameClient(client); + final VLobby lobby = VOnlineLobby.SINGLETON_INSTANCE.setLobby(LobbyType.CLIENT); + client.addLobbyListener(new ILobbyListener() { + @Override public final void update(final LobbyState state) { + lobby.setState(state); + } + @Override public final void message(final String source, final String message) { + FNetOverlay.SINGLETON_INSTANCE.addMessage(source, message); + } + }); + lobby.setPlayerChangeListener(new IPlayerChangeListener() { + @Override public final void update(final LobbyPlayerData data) { + client.send(new LoginEvent(data.getName())); + } + }); client.connect(hostname, port); Singletons.getControl().setCurrentScreen(FScreen.ONLINE_LOBBY); 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 b9a4590b27d..f79ad1fb23f 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 @@ -13,6 +13,7 @@ import forge.gui.framework.FScreen; import forge.gui.framework.IVDoc; import forge.gui.framework.IVTopLevelUI; import forge.screens.home.VLobby; +import forge.screens.home.VLobby.LobbyType; import forge.toolbox.FPanel; import forge.util.gui.SOptionPane; import forge.view.FView; @@ -24,15 +25,19 @@ public enum VOnlineLobby implements IVDoc, IVTopLevelUI { private final DragTab tab = new DragTab("Lobby"); // General variables - private final VLobby lobby; + private VLobby lobby; private VOnlineLobby() { - this.lobby = new VLobby(true); } VLobby getLobby() { return lobby; } + VLobby setLobby(final LobbyType type) { + this.lobby = new VLobby(type); + getLayoutControl().setLobby(lobby); + return this.lobby; + } @Override public void populate() { diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/sanctioned/VSubmenuConstructed.java b/forge-gui-desktop/src/main/java/forge/screens/home/sanctioned/VSubmenuConstructed.java index 12ec7ac2a4d..1aecfad89d3 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/sanctioned/VSubmenuConstructed.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/sanctioned/VSubmenuConstructed.java @@ -13,6 +13,7 @@ import forge.screens.home.EMenuGroup; import forge.screens.home.IVSubmenu; import forge.screens.home.VLobby; import forge.screens.home.VHomeUI; +import forge.screens.home.VLobby.LobbyType; /** * Assembles Swing components of constructed submenu singleton. @@ -27,7 +28,7 @@ public enum VSubmenuConstructed implements IVSubmenu { private DragCell parentCell; private final DragTab tab = new DragTab("Constructed Mode"); - private final VLobby lobby = new VLobby(false); + private final VLobby lobby = new VLobby(LobbyType.LOCAL); private VSubmenuConstructed() { } diff --git a/forge-gui/pom.xml b/forge-gui/pom.xml index a5c2f5593b9..61c8abf75a7 100644 --- a/forge-gui/pom.xml +++ b/forge-gui/pom.xml @@ -46,5 +46,11 @@ commons-lang3 3.3 + + io.netty + netty-all + 4.0.25.Final + compile + diff --git a/forge-gui/src/main/java/forge/interfaces/ILobby.java b/forge-gui/src/main/java/forge/interfaces/ILobby.java new file mode 100644 index 00000000000..a052ac67d9b --- /dev/null +++ b/forge-gui/src/main/java/forge/interfaces/ILobby.java @@ -0,0 +1,10 @@ +package forge.interfaces; + +import forge.net.game.LobbyState; +import forge.net.game.server.RemoteClient; + +public interface ILobby { + LobbyState getState(); + int login(RemoteClient client); + void logout(RemoteClient client); +} diff --git a/forge-gui/src/main/java/forge/interfaces/IPlayerChangeListener.java b/forge-gui/src/main/java/forge/interfaces/IPlayerChangeListener.java new file mode 100644 index 00000000000..49e0821da3f --- /dev/null +++ b/forge-gui/src/main/java/forge/interfaces/IPlayerChangeListener.java @@ -0,0 +1,7 @@ +package forge.interfaces; + +import forge.net.game.LobbyState.LobbyPlayerData; + +public interface IPlayerChangeListener { + void update(LobbyPlayerData data); +} diff --git a/forge-gui/src/main/java/forge/net/FGameClient.java b/forge-gui/src/main/java/forge/net/FGameClient.java index 22a6d03e1a0..c9d2ea9bb17 100644 --- a/forge-gui/src/main/java/forge/net/FGameClient.java +++ b/forge-gui/src/main/java/forge/net/FGameClient.java @@ -14,15 +14,23 @@ import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.serialization.ClassResolvers; import io.netty.handler.codec.serialization.ObjectDecoder; import io.netty.handler.codec.serialization.ObjectEncoder; -import forge.GuiBase; + +import java.util.List; + +import com.google.common.collect.Lists; + import forge.game.GameView; import forge.game.player.PlayerView; import forge.interfaces.IGuiGame; +import forge.model.FModel; import forge.net.game.GuiGameEvent; +import forge.net.game.LobbyUpdateEvent; import forge.net.game.LoginEvent; import forge.net.game.MessageEvent; import forge.net.game.NetEvent; +import forge.net.game.client.ILobbyListener; import forge.net.game.client.IToServer; +import forge.properties.ForgePreferences.FPref; public class FGameClient implements IToServer { private final IGuiGame clientGui; @@ -30,6 +38,8 @@ public class FGameClient implements IToServer { this.clientGui = clientGui; } + private final List lobbyListeners = Lists.newArrayList(); + static final int SIZE = Integer.parseInt(System.getProperty("size", "256")); private Channel channel; @@ -47,6 +57,7 @@ public class FGameClient implements IToServer { new ObjectEncoder(), new ObjectDecoder(ClassResolvers.cacheDisabled(null)), new MessageHandler(), + new LobbyUpdateHandler(), new GameClientHandler()); } }); @@ -74,6 +85,10 @@ public class FGameClient implements IToServer { channel.writeAndFlush(event); } + public void addLobbyListener(final ILobbyListener listener) { + lobbyListeners.add(listener); + } + private class GameClientHandler extends ChannelInboundHandlerAdapter { /** * Creates a client-side handler. @@ -83,7 +98,8 @@ public class FGameClient implements IToServer { @Override public void channelActive(final ChannelHandlerContext ctx) { - send(new LoginEvent("elcnesh")); + // Don't use send here, as this.channel is not yet set! + ctx.channel().writeAndFlush(new LoginEvent(FModel.getPreferences().getPref(FPref.PLAYER_NAME))); } @SuppressWarnings("unchecked") @@ -116,7 +132,21 @@ public class FGameClient implements IToServer { public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception { if (msg instanceof MessageEvent) { final MessageEvent event = (MessageEvent) msg; - GuiBase.getInterface().netMessage(event.getSource(), event.getMessage()); + for (final ILobbyListener listener : lobbyListeners) { + listener.message(event.getSource(), event.getMessage()); + } + } + super.channelRead(ctx, msg); + } + } + + private class LobbyUpdateHandler extends ChannelInboundHandlerAdapter { + @Override + public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception { + if (msg instanceof LobbyUpdateEvent) { + for (final ILobbyListener listener : lobbyListeners) { + listener.update(((LobbyUpdateEvent) msg).getState()); + } } super.channelRead(ctx, msg); } diff --git a/forge-gui/src/main/java/forge/net/FServerManager.java b/forge-gui/src/main/java/forge/net/FServerManager.java index f232a576804..a1914c79003 100644 --- a/forge-gui/src/main/java/forge/net/FServerManager.java +++ b/forge-gui/src/main/java/forge/net/FServerManager.java @@ -1,12 +1,14 @@ package forge.net; import forge.game.GameRules; +import forge.interfaces.ILobby; +import forge.net.game.LobbyState; +import forge.net.game.LobbyUpdateEvent; import forge.net.game.LoginEvent; import forge.net.game.LogoutEvent; import forge.net.game.MessageEvent; import forge.net.game.NetEvent; import forge.net.game.RegisterDeckEvent; -import forge.net.game.client.ILobbyListener; import forge.net.game.server.RemoteClient; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; @@ -25,10 +27,8 @@ import io.netty.handler.codec.serialization.ObjectEncoder; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; -import java.util.List; import java.util.Map; -import com.google.common.collect.Lists; import com.google.common.collect.Maps; public final class FServerManager { @@ -39,7 +39,7 @@ public final class FServerManager { private final Map games = Maps.newTreeMap(); private int id = 0; private final Map clients = Maps.newTreeMap(); - private final List lobbyListeners = Lists.newArrayListWithExpectedSize(1); + private ILobby localLobby; private FServerManager() { } @@ -102,12 +102,13 @@ public final class FServerManager { public void broadcast(final NetEvent event) { for (final RemoteClient client : clients.values()) { + event.updateForClient(client); client.send(event); } } - public void registerLobbyListener(final ILobbyListener lobbyListener) { - lobbyListeners.add(lobbyListener); + public void setLobby(final ILobby lobby) { + this.localLobby = lobby; } public NetGame hostGame(final GameRules rules) { @@ -117,6 +118,12 @@ public final class FServerManager { return game; } + public void updateLobbyState() { + final LobbyState state = localLobby.getState(); + final LobbyUpdateEvent event = new LobbyUpdateEvent(state); + broadcast(event); + } + @Override protected void finalize() throws Throwable { super.finalize(); @@ -153,6 +160,7 @@ public final class FServerManager { clients.put(ctx.channel(), client); games.get(0).addClient(client); System.out.println("User connected to server at " + ctx.channel().remoteAddress()); + updateLobbyState(); super.channelActive(ctx); } @@ -161,6 +169,7 @@ public final class FServerManager { final RemoteClient client = clients.get(ctx.channel()); if (msg instanceof LoginEvent) { client.setUsername(((LoginEvent) msg).getUsername()); + updateLobbyState(); } else if (msg instanceof RegisterDeckEvent) { games.get(0).registerDeck(client, ((RegisterDeckEvent) msg).getDeck()); } @@ -173,15 +182,15 @@ public final class FServerManager { final RemoteClient client = clients.get(ctx.channel()); if (msg instanceof LoginEvent) { final LoginEvent event = (LoginEvent) msg; - for (final ILobbyListener lobbyListener : lobbyListeners) { - lobbyListener.login(client); + final int index = localLobby.login(client); + if (index == -1) { + ctx.close(); + } else { + client.setIndex(index); + broadcast(event); } - broadcast(event); } else if (msg instanceof MessageEvent) { final MessageEvent event = (MessageEvent) msg; - for (final ILobbyListener lobbyListener : lobbyListeners) { - lobbyListener.message(client.getUsername(), event.getMessage()); - } broadcast(event); } super.channelRead(ctx, msg); @@ -189,9 +198,8 @@ public final class FServerManager { @Override public void channelInactive(final ChannelHandlerContext ctx) throws Exception { final RemoteClient client = clients.get(ctx.channel()); - for (final ILobbyListener lobbyListener : lobbyListeners) { - lobbyListener.logout(client); - } + localLobby.logout(client); + updateLobbyState(); super.channelInactive(ctx); } } @@ -205,4 +213,5 @@ public final class FServerManager { super.channelInactive(ctx); } } + } diff --git a/forge-net/src/main/java/forge/net/game/GuiGameEvent.java b/forge-net/src/main/java/forge/net/game/GuiGameEvent.java index d6cba6d9a73..5d4c81b7a59 100644 --- a/forge-net/src/main/java/forge/net/game/GuiGameEvent.java +++ b/forge-net/src/main/java/forge/net/game/GuiGameEvent.java @@ -3,9 +3,11 @@ package forge.net.game; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; +import forge.net.game.server.RemoteClient; import forge.trackable.TrackableObject; public final class GuiGameEvent implements NetEvent { + private static final long serialVersionUID = 6223690008522514574L; private final String method; private final Iterable objects; @@ -15,6 +17,10 @@ public final class GuiGameEvent implements NetEvent { this.objects = objects == null ? ImmutableSet.of() : objects; } + @Override + public void updateForClient(final RemoteClient client) { + } + public String getMethod() { return method; } diff --git a/forge-net/src/main/java/forge/net/game/LobbySlotType.java b/forge-net/src/main/java/forge/net/game/LobbySlotType.java new file mode 100644 index 00000000000..49d21b8ea82 --- /dev/null +++ b/forge-net/src/main/java/forge/net/game/LobbySlotType.java @@ -0,0 +1,8 @@ +package forge.net.game; + +public enum LobbySlotType { + LOCAL, + AI, + OPEN, + REMOTE; +} \ No newline at end of file diff --git a/forge-net/src/main/java/forge/net/game/LobbyState.java b/forge-net/src/main/java/forge/net/game/LobbyState.java new file mode 100644 index 00000000000..eff30a2de55 --- /dev/null +++ b/forge-net/src/main/java/forge/net/game/LobbyState.java @@ -0,0 +1,51 @@ +package forge.net.game; + +import java.io.Serializable; +import java.util.Collections; +import java.util.List; + +import com.google.common.collect.Lists; + +public class LobbyState implements Serializable { + private static final long serialVersionUID = 3899410700896996173L; + + private final List players = Lists.newArrayList(); + private int localPlayer = -1; + public int getLocalPlayer() { + return localPlayer; + } + public void setLocalPlayer(final int localPlayer) { + this.localPlayer = localPlayer; + } + + public void addPlayer(final LobbyPlayerData data) { + players.add(data); + } + public List getPlayers() { + return Collections.unmodifiableList(players); + } + + public final static class LobbyPlayerData implements Serializable { + private static final long serialVersionUID = 8642923786206592216L; + + private final String name; + private final int avatarIndex; + private final LobbySlotType type; + public LobbyPlayerData(final String name, final int avatarIndex, final LobbySlotType type) { + this.name = name; + this.avatarIndex = avatarIndex; + this.type = type; + } + + public String getName() { + return name; + } + public int getAvatarIndex() { + return avatarIndex; + } + public LobbySlotType getType() { + return type; + } + + } +} diff --git a/forge-net/src/main/java/forge/net/game/LobbyUpdateEvent.java b/forge-net/src/main/java/forge/net/game/LobbyUpdateEvent.java new file mode 100644 index 00000000000..3196498c903 --- /dev/null +++ b/forge-net/src/main/java/forge/net/game/LobbyUpdateEvent.java @@ -0,0 +1,21 @@ +package forge.net.game; + +import forge.net.game.server.RemoteClient; + +public class LobbyUpdateEvent implements NetEvent { + private static final long serialVersionUID = -3176971304173703949L; + + private final LobbyState state; + public LobbyUpdateEvent(final LobbyState state) { + this.state = state; + } + + @Override + public void updateForClient(final RemoteClient client) { + state.setLocalPlayer(client.getIndex()); + } + + public LobbyState getState() { + return state; + } +} diff --git a/forge-net/src/main/java/forge/net/game/LoginEvent.java b/forge-net/src/main/java/forge/net/game/LoginEvent.java index 52e21bf488e..4b7d2be35b2 100644 --- a/forge-net/src/main/java/forge/net/game/LoginEvent.java +++ b/forge-net/src/main/java/forge/net/game/LoginEvent.java @@ -1,5 +1,7 @@ package forge.net.game; +import forge.net.game.server.RemoteClient; + public class LoginEvent implements NetEvent { private static final long serialVersionUID = -8865183377417377938L; @@ -8,6 +10,10 @@ public class LoginEvent implements NetEvent { this.username = username; } + @Override + public void updateForClient(final RemoteClient client) { + } + public String getUsername() { return username; } diff --git a/forge-net/src/main/java/forge/net/game/LogoutEvent.java b/forge-net/src/main/java/forge/net/game/LogoutEvent.java index be52cf36183..1c2214811a4 100644 --- a/forge-net/src/main/java/forge/net/game/LogoutEvent.java +++ b/forge-net/src/main/java/forge/net/game/LogoutEvent.java @@ -1,5 +1,7 @@ package forge.net.game; +import forge.net.game.server.RemoteClient; + public class LogoutEvent implements NetEvent { private static final long serialVersionUID = -8262613254026625787L; @@ -8,6 +10,9 @@ public class LogoutEvent implements NetEvent { this.username = username; } + public void updateForClient(final RemoteClient client) { + } + public String getUsername() { return username; } diff --git a/forge-net/src/main/java/forge/net/game/MessageEvent.java b/forge-net/src/main/java/forge/net/game/MessageEvent.java index 080770c6123..9d63fd13067 100644 --- a/forge-net/src/main/java/forge/net/game/MessageEvent.java +++ b/forge-net/src/main/java/forge/net/game/MessageEvent.java @@ -1,5 +1,7 @@ package forge.net.game; +import forge.net.game.server.RemoteClient; + public class MessageEvent implements NetEvent { private static final long serialVersionUID = 1700060210647684186L; @@ -8,6 +10,8 @@ public class MessageEvent implements NetEvent { this.source = source; this.message = message; } + public void updateForClient(final RemoteClient client) { + } public String getSource() { return source; diff --git a/forge-net/src/main/java/forge/net/game/NetEvent.java b/forge-net/src/main/java/forge/net/game/NetEvent.java index 4a6acd4acc2..6db9575e83e 100644 --- a/forge-net/src/main/java/forge/net/game/NetEvent.java +++ b/forge-net/src/main/java/forge/net/game/NetEvent.java @@ -2,5 +2,8 @@ package forge.net.game; import java.io.Serializable; +import forge.net.game.server.RemoteClient; + public interface NetEvent extends Serializable { + void updateForClient(RemoteClient client); } diff --git a/forge-net/src/main/java/forge/net/game/RegisterDeckEvent.java b/forge-net/src/main/java/forge/net/game/RegisterDeckEvent.java index 3b3e2da248c..e7c42dd100b 100644 --- a/forge-net/src/main/java/forge/net/game/RegisterDeckEvent.java +++ b/forge-net/src/main/java/forge/net/game/RegisterDeckEvent.java @@ -1,6 +1,7 @@ package forge.net.game; import forge.deck.Deck; +import forge.net.game.server.RemoteClient; public class RegisterDeckEvent implements NetEvent { private static final long serialVersionUID = -6553476654530937343L; @@ -9,6 +10,8 @@ public class RegisterDeckEvent implements NetEvent { public RegisterDeckEvent(final Deck deck) { this.deck = deck; } + public void updateForClient(final RemoteClient client) { + } public final Deck getDeck() { return deck; diff --git a/forge-net/src/main/java/forge/net/game/client/ILobbyListener.java b/forge-net/src/main/java/forge/net/game/client/ILobbyListener.java index 8443d061e3c..090fce2cfdb 100644 --- a/forge-net/src/main/java/forge/net/game/client/ILobbyListener.java +++ b/forge-net/src/main/java/forge/net/game/client/ILobbyListener.java @@ -1,9 +1,8 @@ package forge.net.game.client; -import forge.net.game.server.RemoteClient; +import forge.net.game.LobbyState; public interface ILobbyListener { - void login(RemoteClient client); - void logout(RemoteClient client); void message(String source, String message); + void update(LobbyState state); } diff --git a/forge-net/src/main/java/forge/net/game/server/RemoteClient.java b/forge-net/src/main/java/forge/net/game/server/RemoteClient.java index 3bee871f98d..2f18a5df21a 100644 --- a/forge-net/src/main/java/forge/net/game/server/RemoteClient.java +++ b/forge-net/src/main/java/forge/net/game/server/RemoteClient.java @@ -7,6 +7,7 @@ public final class RemoteClient implements IToClient { private final Channel channel; private String username; + private int index; public RemoteClient(final Channel channel) { this.channel = channel; } @@ -22,4 +23,11 @@ public final class RemoteClient implements IToClient { public void setUsername(final String username) { this.username = username; } + + public int getIndex() { + return index; + } + public void setIndex(final int index) { + this.index = index; + } }