GUI: improved main and battlefield menus (related to #11533):

- main menu: better naming for deck editor;
- battlefield menu: added deck view for computer players, better naming;
- network: improved experimental connection;
This commit is contained in:
Oleg Agafonov 2023-12-09 15:20:29 +04:00
parent ba8650b4e2
commit 6aaaa8cb46
11 changed files with 187 additions and 132 deletions

View file

@ -614,6 +614,8 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
Component[] windows = desktopPane.getComponentsInLayer(javax.swing.JLayeredPane.DEFAULT_LAYER);
MagePaneMenuItem menuItem;
// TODO: sort menu by games, not current component order
// lobby -> table 1 tourny, table 1 draft, table 1 game, table 2...
for (int i = 0; i < windows.length; i++) {
if (windows[i] instanceof MagePane) {
MagePane window = (MagePane) windows[i];
@ -1284,34 +1286,59 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
}
}
public void showDeckEditor(DeckEditorMode mode, Deck deck, UUID tableId, int time) {
private String prepareDeckEditorName(DeckEditorMode mode, Deck deck, UUID tableId) {
// GUI searching frame name for duplicates, so:
// - online editors must be unique;
// - offline editor must be single;
String name;
if (mode == DeckEditorMode.SIDEBOARDING
|| mode == DeckEditorMode.LIMITED_BUILDING
|| mode == DeckEditorMode.LIMITED_SIDEBOARD_BUILDING
|| mode == DeckEditorMode.VIEW_LIMITED_DECK) {
name = "Deck Editor - " + tableId.toString();
} else {
if (deck != null) {
name = "Deck Editor - " + deck.getName();
} else {
switch (mode) {
case FREE_BUILDING:
// offline
name = "Deck Editor";
}
// use already open editor
Component[] windows = desktopPane.getComponentsInLayer(JLayeredPane.DEFAULT_LAYER);
for (Component window : windows) {
if (window instanceof DeckEditorPane && ((MagePane) window).getTitle().equals(name)) {
setActive((MagePane) window);
return;
}
break;
case LIMITED_BUILDING:
case LIMITED_SIDEBOARD_BUILDING:
case SIDEBOARDING:
case VIEW_LIMITED_DECK:
// online
name = "Deck Editor - " + mode.getTitle();
break;
default:
throw new IllegalArgumentException("Unknown deck editor mode: " + mode);
}
// additional info about deck/player
if (deck != null && deck.getName() != null && !deck.getName().isEmpty()) {
name += " - " + deck.getName();
}
// additional info about game
if (tableId != null) {
name += " - table " + tableId;
}
return name;
}
public void showDeckEditor(DeckEditorMode mode, Deck deck, UUID tableId, int visibleTimer) {
// create or open new editor
String name = prepareDeckEditorName(mode, deck, tableId);
// already exists
Component[] windows = desktopPane.getComponentsInLayer(JLayeredPane.DEFAULT_LAYER);
for (Component window : windows) {
if (window instanceof DeckEditorPane && ((MagePane) window).getTitle().equals(name)) {
setActive((MagePane) window);
return;
}
}
DeckEditorPane deckEditorPane = new DeckEditorPane();
desktopPane.add(deckEditorPane, JLayeredPane.DEFAULT_LAYER);
deckEditorPane.setVisible(false);
deckEditorPane.show(mode, deck, name, tableId, time);
setActive(deckEditorPane);
// new editor
DeckEditorPane deckEditor = new DeckEditorPane();
desktopPane.add(deckEditor, JLayeredPane.DEFAULT_LAYER);
deckEditor.setVisible(false);
deckEditor.show(mode, deck, name, tableId, visibleTimer);
setActive(deckEditor);
}
public void showUserRequestDialog(final UserRequestMessage userRequestMessage) {

View file

@ -104,8 +104,8 @@ public final class SessionHandler {
session.connectStop(askForReconnect, keepMySessionActive);
}
public static void sendPlayerAction(PlayerAction playerAction, UUID gameId, Object relatedUserId) {
session.sendPlayerAction(playerAction, gameId, relatedUserId);
public static void sendPlayerAction(PlayerAction playerAction, UUID gameId, Object data) {
session.sendPlayerAction(playerAction, gameId, data);
}
public static void quitTournament(UUID tournamentId) {

View file

@ -101,12 +101,21 @@ public final class Constants {
}
public enum DeckEditorMode {
FREE_BUILDING(""),
LIMITED_BUILDING("building"),
LIMITED_SIDEBOARD_BUILDING("sideboard building"),
SIDEBOARDING("sideboarding"),
VIEW_LIMITED_DECK("view");
FREE_BUILDING,
LIMITED_BUILDING,
LIMITED_SIDEBOARD_BUILDING,
SIDEBOARDING,
VIEW_LIMITED_DECK
private String title;
DeckEditorMode(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
}
public enum SortBy {

View file

@ -45,20 +45,10 @@ public class DeckEditorPane extends MagePane {
deckEditorPanel1.changeGUISize();
}
public void show(DeckEditorMode mode, Deck deck, String name, UUID tableId, int time) {
public void show(DeckEditorMode mode, Deck deck, String name, UUID tableId, int visibleTimer) {
this.tableId = tableId;
if (mode == DeckEditorMode.SIDEBOARDING
|| mode == DeckEditorMode.LIMITED_BUILDING
|| mode == DeckEditorMode.LIMITED_SIDEBOARD_BUILDING) {
this.setTitle("Deck Editor - " + tableId.toString());
} else if (mode == DeckEditorMode.VIEW_LIMITED_DECK) {
this.setTitle("Deck Editor - Current Deck");
} else if (deck != null) {
this.setTitle("Deck Editor - " + deck.getName());
} else {
this.setTitle("Deck Editor");
}
this.deckEditorPanel1.showDeckEditor(mode, deck, tableId, time);
this.setTitle(name);
this.deckEditorPanel1.showDeckEditor(mode, deck, tableId, visibleTimer);
this.repaint();
}

View file

@ -200,7 +200,7 @@ public class DeckEditorPanel extends javax.swing.JPanel {
this.deckArea.changeGUISize();
}
public void showDeckEditor(DeckEditorMode mode, Deck deck, UUID tableId, int time) {
public void showDeckEditor(DeckEditorMode mode, Deck deck, UUID tableId, int visibleTimer) {
if (deck != null) {
this.deck = deck;
}
@ -236,7 +236,7 @@ public class DeckEditorPanel extends javax.swing.JPanel {
}
this.deckArea.showSideboard(true);
countdown.stop();
this.timeout = time;
this.timeout = visibleTimer;
setTimeout(timeout);
if (timeout != 0) {
countdown.start();
@ -278,8 +278,8 @@ public class DeckEditorPanel extends javax.swing.JPanel {
this.cardSelector.setVisible(false);
this.deckArea.setOrientation(/*limitedBuildingOrientation = */true);
this.deckArea.showSideboard(true);
this.lblDeckName.setVisible(false);
this.txtDeckName.setVisible(false);
this.lblDeckName.setVisible(true);
this.txtDeckName.setVisible(true);
this.txtTimeRemaining.setVisible(false);
break;
}

View file

@ -1,6 +1,5 @@
package mage.client.game;
import mage.cards.decks.importer.DeckImporter;
import mage.client.MageFrame;
import mage.client.SessionHandler;
import mage.client.cards.BigCard;
@ -32,6 +31,7 @@ public class PlayAreaPanel extends javax.swing.JPanel {
private final JPopupMenu popupMenu;
private UUID playerId;
private String playerName;
private UUID gameId;
private boolean isMe = false;
private boolean smallMode = false;
@ -67,6 +67,11 @@ public class PlayAreaPanel extends javax.swing.JPanel {
setOpaque(false);
battlefieldPanel.setOpaque(false);
// data init
init(player, bigCard, gameId, priorityTime);
update(null, player, null);
// init popup menu (must run after data init)
popupMenu = new JPopupMenu();
if (options.isPlayer) {
addPopupMenuPlayer(player.getUserData().isAllowRequestHandToAll());
@ -74,10 +79,8 @@ public class PlayAreaPanel extends javax.swing.JPanel {
addPopupMenuWatcher();
}
this.add(popupMenu);
setGUISize();
init(player, bigCard, gameId, priorityTime);
update(null, player, null);
setGUISize();
}
public void CleanUp() {
@ -311,49 +314,16 @@ public class PlayAreaPanel extends javax.swing.JPanel {
// Reset the replacement and yes/no dialogs that were auto selected for the game
menuItem.addActionListener(e -> SessionHandler.sendPlayerAction(PlayerAction.REQUEST_AUTO_ANSWER_RESET_ALL, gameId, null));
JMenu handCardsMenu = new JMenu("Cards on hand");
handCardsMenu.setMnemonic(KeyEvent.VK_H);
popupMenu.add(handCardsMenu);
if (!options.playerItself) {
menuItem = new JMenuItem("Request permission to see the hand cards");
menuItem.setMnemonic(KeyEvent.VK_P);
handCardsMenu.add(menuItem);
// Request to see hand cards
menuItem.addActionListener(e -> SessionHandler.sendPlayerAction(PlayerAction.REQUEST_PERMISSION_TO_SEE_HAND_CARDS, gameId, playerId));
} else {
allowViewHandCardsMenuItem = new JCheckBoxMenuItem("Allow hand requests from other users", allowRequestToShowHandCards);
allowViewHandCardsMenuItem.setMnemonic(KeyEvent.VK_A);
allowViewHandCardsMenuItem.setToolTipText("Watchers or other players can request your hand cards once per game. Re-activate it to allow new requests.");
handCardsMenu.add(allowViewHandCardsMenuItem);
// requests allowed (disable -> enable to reset requested list)
allowViewHandCardsMenuItem.addActionListener(e -> {
boolean requestsAllowed = ((JCheckBoxMenuItem) e.getSource()).getState();
PreferencesDialog.setPrefValue(KEY_GAME_ALLOW_REQUEST_SHOW_HAND_CARDS, requestsAllowed);
SessionHandler.sendPlayerAction(requestsAllowed ? PlayerAction.PERMISSION_REQUESTS_ALLOWED_ON : PlayerAction.PERMISSION_REQUESTS_ALLOWED_OFF, gameId, null);
});
menuItem = new JMenuItem("Revoke all permission(s) to see your hand cards");
menuItem.setMnemonic(KeyEvent.VK_R);
menuItem.setToolTipText("Revoke already granted permission for all spectators to see your hand cards.");
handCardsMenu.add(menuItem);
// revoke permissions to see hand cards
menuItem.addActionListener(e -> SessionHandler.sendPlayerAction(PlayerAction.REVOKE_PERMISSIONS_TO_SEE_HAND_CARDS, gameId, null));
}
JMenu rollbackMainItem = new JMenu("Rollback");
rollbackMainItem.setToolTipText("The game will be rolled back to the start of the requested turn if all players agree");
popupMenu.add(rollbackMainItem);
if (options.rollbackTurnsAllowed) {
ActionListener rollBackActionListener = e -> {
int turnsToRollBack = Integer.parseInt(e.getActionCommand());
SessionHandler.sendPlayerAction(PlayerAction.ROLLBACK_TURNS, gameId, turnsToRollBack);
};
JMenu rollbackMainItem = new JMenu("Rollback");
rollbackMainItem.setMnemonic(KeyEvent.VK_R);
rollbackMainItem.setToolTipText("The game will be rolled back to the start of the requested turn if all players agree.");
popupMenu.add(rollbackMainItem);
menuItem = new JMenuItem("To the start of the current turn");
menuItem.setMnemonic(KeyEvent.VK_C);
@ -378,7 +348,8 @@ public class PlayAreaPanel extends javax.swing.JPanel {
menuItem.setActionCommand("3");
menuItem.addActionListener(rollBackActionListener);
rollbackMainItem.add(menuItem);
} else {
rollbackMainItem.setText(rollbackMainItem.getText() + ", restricted");
}
JMenu concedeMenu = new JMenu("Concede");
@ -443,30 +414,65 @@ public class PlayAreaPanel extends javax.swing.JPanel {
}
});
popupMenu.addSeparator();
// view deck
menuItem = new JMenuItem("<html>View player's deck");
menuItem.setMnemonic(KeyEvent.VK_D);
// view hands
JMenu handCardsMenu = new JMenu(String.format("Cards on hand (%s)", (options.playerItself ? "me" : playerName)));
handCardsMenu.setMnemonic(KeyEvent.VK_H);
popupMenu.add(handCardsMenu);
if (!options.playerItself) {
menuItem = new JMenuItem("Request permission to see the hand cards");
menuItem.setMnemonic(KeyEvent.VK_P);
handCardsMenu.add(menuItem);
// Request to see hand cards
menuItem.addActionListener(e -> SessionHandler.sendPlayerAction(PlayerAction.REQUEST_PERMISSION_TO_SEE_HAND_CARDS, gameId, playerId));
} else {
allowViewHandCardsMenuItem = new JCheckBoxMenuItem("Allow hand requests from other users", allowRequestToShowHandCards);
allowViewHandCardsMenuItem.setMnemonic(KeyEvent.VK_A);
allowViewHandCardsMenuItem.setToolTipText("Watchers or other players can request your hand cards once per game. Re-activate it to allow new requests.");
handCardsMenu.add(allowViewHandCardsMenuItem);
// requests allowed (disable -> enable to reset requested list)
allowViewHandCardsMenuItem.addActionListener(e -> {
boolean requestsAllowed = ((JCheckBoxMenuItem) e.getSource()).getState();
PreferencesDialog.setPrefValue(KEY_GAME_ALLOW_REQUEST_SHOW_HAND_CARDS, requestsAllowed);
SessionHandler.sendPlayerAction(requestsAllowed ? PlayerAction.PERMISSION_REQUESTS_ALLOWED_ON : PlayerAction.PERMISSION_REQUESTS_ALLOWED_OFF, gameId, null);
});
menuItem = new JMenuItem("Revoke all permission(s) to see your hand cards");
menuItem.setMnemonic(KeyEvent.VK_R);
menuItem.setToolTipText("Revoke already granted permission for all spectators to see your hand cards.");
handCardsMenu.add(menuItem);
// revoke permissions to see hand cards
menuItem.addActionListener(e -> SessionHandler.sendPlayerAction(PlayerAction.REVOKE_PERMISSIONS_TO_SEE_HAND_CARDS, gameId, null));
}
// view deck (allows to view only own deck or computer)
// it's a client side checks... same checks must be on server side too (see PlayerView)
menuItem = new JMenuItem(String.format("<html>View deck (%s)", (options.playerItself ? "me" : playerName)));
popupMenu.add(menuItem);
menuItem.addActionListener(e -> {
SessionHandler.sendPlayerAction(PlayerAction.VIEW_LIMITED_DECK, gameId, null);
});
if (options.playerItself || !options.isHuman) {
menuItem.setMnemonic(KeyEvent.VK_D);
menuItem.addActionListener(e -> {
SessionHandler.sendPlayerAction(PlayerAction.VIEW_LIMITED_DECK, gameId, playerId);
});
} else {
menuItem.setText(menuItem.getText() + ", restricted");
}
// view sideboard (allows to view only own sideboard or computer)
// it's a client side checks... same checks must be on server side too (see PlayerView)
menuItem = new JMenuItem(String.format("<html>View sideboard (%s)", (options.playerItself ? "me" : playerName)));
popupMenu.add(menuItem);
if (options.playerItself || !options.isHuman) {
String menuCaption = "<html>View my sideboard";
if (!options.isHuman) {
menuCaption = "<html>View computer's sideboard";
}
menuItem = new JMenuItem(menuCaption);
menuItem.setMnemonic(KeyEvent.VK_S);
popupMenu.add(menuItem);
menuItem.addActionListener(e -> {
SessionHandler.sendPlayerAction(PlayerAction.VIEW_SIDEBOARD, gameId, playerId);
});
} else {
menuItem.setText(menuItem.getText() + ", restricted");
}
}
@ -485,10 +491,9 @@ public class PlayAreaPanel extends javax.swing.JPanel {
MageFrame.getInstance().showUserRequestDialog(message);
});
menuItem = new JMenuItem("Request permission to see hand cards");
popupMenu.add(menuItem);
// Request to see hand cards
menuItem = new JMenuItem(String.format("Request permission to see hand cards (%s)", playerName));
popupMenu.add(menuItem);
menuItem.addActionListener(e -> SessionHandler.sendPlayerAction(PlayerAction.REQUEST_PERMISSION_TO_SEE_HAND_CARDS, gameId, playerId));
battlefieldPanel.getMainPanel().addMouseListener(new MouseAdapter() {
@ -517,6 +522,7 @@ public class PlayAreaPanel extends javax.swing.JPanel {
this.battlefieldPanel.init(gameId, bigCard);
this.gameId = gameId;
this.playerId = player.getPlayerId();
this.playerName = player.getName();
this.isMe = player.getControlled();
this.btnCheat.setVisible(SessionHandler.isTestMode());
}

View file

@ -24,6 +24,7 @@ public final class DeckUtil {
public static Deck construct(DeckView view) {
Deck deck = new Deck();
deck.setName(view.getName());
for (SimpleCardView cardView : view.getCards().values()) {
CardInfo cardInfo = CardRepository.instance.findCard(cardView.getExpansionSetCode(), cardView.getCardNumber());
Card card = cardInfo != null ? cardInfo.getMockCard() : null;

View file

@ -54,9 +54,9 @@ public interface GamePlay {
*
* @param passPriorityAction
* @param gameId
* @param Data
* @param data
* @return
*/
boolean sendPlayerAction(PlayerAction passPriorityAction, UUID gameId, Object Data);
boolean sendPlayerAction(PlayerAction passPriorityAction, UUID gameId, Object data);
}

View file

@ -436,7 +436,9 @@ public class Session {
call.setMessageId(messageId.incrementAndGet());
lockSet = true;
Callback callback = new Callback(call);
callbackHandler.handleCallbackOneway(callback, SUPER_DUPER_BUGGY_AND_FASTEST_ASYNC_CONNECTION);
boolean sendAsync = SUPER_DUPER_BUGGY_AND_FASTEST_ASYNC_CONNECTION
&& call.getMethod().getType().canComeInAnyOrder();
callbackHandler.handleCallbackOneway(callback, sendAsync);
}
} catch (InterruptedException ex) {
// already sending another command (connection problem?)

View file

@ -612,12 +612,15 @@ public class GameController implements GameCallback {
}
break;
case VIEW_LIMITED_DECK:
viewLimitedDeck(getPlayerId(userId), userId);
if (data instanceof UUID) {
UUID targetPlayerId = (UUID) data;
viewDeckOrSideboard(getPlayerId(userId), userId, targetPlayerId, false);
}
break;
case VIEW_SIDEBOARD:
if (data instanceof UUID) {
UUID targetPlayerId = (UUID) data;
viewSideboard(getPlayerId(userId), userId, targetPlayerId);
viewDeckOrSideboard(getPlayerId(userId), userId, targetPlayerId, true);
}
break;
default:
@ -678,31 +681,37 @@ public class GameController implements GameCallback {
}
}
private void viewLimitedDeck(UUID playerId, UUID userId) {
Player viewLimitedDeckPlayer = game.getPlayer(playerId);
if (viewLimitedDeckPlayer != null) {
if (viewLimitedDeckPlayer.isHuman()) {
for (MatchPlayer p : managerFactory.tableManager().getTable(tableId).getMatch().getPlayers()) {
if (p.getPlayer().getId().equals(playerId)) {
Optional<User> u = managerFactory.userManager().getUser(userId);
if (u.isPresent() && p.getDeck() != null) {
u.get().ccViewLimitedDeck(p.getDeck(), tableId, requestsOpen, true);
}
}
}
}
private void viewDeckOrSideboard(UUID playerId, UUID userId, UUID targetPlayerId, boolean isSideboardOnly) {
Player requestPlayer = game.getPlayer(playerId);
Player targetPlayer = game.getPlayer(targetPlayerId);
if (requestPlayer == null || targetPlayer == null) {
return;
}
}
private void viewSideboard(UUID playerId, UUID userId, UUID targetPlayerId) {
Player needPlayer = game.getPlayer(playerId);
if (needPlayer != null && needPlayer.isHuman()) {
for (MatchPlayer p : managerFactory.tableManager().getTable(tableId).getMatch().getPlayers()) {
if (p.getPlayer().getId().equals(playerId)) {
Optional<User> u = managerFactory.userManager().getUser(userId);
u.ifPresent(user -> user.ccViewSideboard(tableId, game.getId(), targetPlayerId));
}
}
// allows for itself or computers only
// TODO: implement allow deck view for all (same as allow hand view for all)
if (!requestPlayer.getId().equals(targetPlayer.getId()) && !targetPlayer.isComputer()) {
logger.error("Player " + requestPlayer.getName() + " trying to cheat with deck/sideboard view");
// TODO: inform other players about cheating?
return;
}
User user = managerFactory.userManager().getUser(userId).orElse(null);
Table table = managerFactory.tableManager().getTable(tableId);
if (user == null || table == null) {
return;
}
MatchPlayer deckSource = table.getMatch().getPlayer(targetPlayerId);
if (deckSource == null) {
return;
}
if (isSideboardOnly) {
// sideboard data already sent in PlayerView, so no need to re-sent it TODO: re-sent deck instead?
user.ccViewSideboard(tableId, game.getId(), targetPlayerId);
} else {
user.ccViewLimitedDeck(deckSource.getDeckForViewer(), tableId, requestsOpen, true);
}
}

View file

@ -78,6 +78,17 @@ public class MatchPlayer implements Serializable {
return deck;
}
public Deck getDeckForViewer() {
if (this.deck == null) {
return null;
}
// Tiny Leaders uses deck name for game, also must hide real deck name from other players
Deck viewerDeck = this.deck.copy();
viewerDeck.setName(this.getName());
return viewerDeck;
}
public void submitDeck(Deck deck) {
this.deck = deck;
this.doneSideboarding = true;