diff --git a/Mage.Client/src/main/java/mage/client/dialog/CustomOptionsDialog.form b/Mage.Client/src/main/java/mage/client/dialog/CustomOptionsDialog.form index a19c7607bde..ee584f9b325 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/CustomOptionsDialog.form +++ b/Mage.Client/src/main/java/mage/client/dialog/CustomOptionsDialog.form @@ -22,11 +22,26 @@ - + - - + + + + + + + + + + + + + + + + + @@ -35,20 +50,21 @@ - + - + - - + + + + + - - - - + + @@ -76,6 +92,28 @@ + + + + + + + + + + + + + + + + + + + + + + @@ -173,7 +211,7 @@ - + @@ -185,6 +223,19 @@ + + + + + + + + + + + + + @@ -195,5 +246,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Mage.Client/src/main/java/mage/client/dialog/CustomOptionsDialog.java b/Mage.Client/src/main/java/mage/client/dialog/CustomOptionsDialog.java index f7c9495a3c5..29d4759abfb 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/CustomOptionsDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/CustomOptionsDialog.java @@ -1,12 +1,21 @@ package mage.client.dialog; +import mage.cards.decks.Deck; +import mage.cards.decks.DeckFileFilter; +import mage.cards.decks.importer.DeckImporter; +import mage.client.MageFrame; import mage.constants.MultiplayerAttackOption; import mage.constants.RangeOfInfluence; +import mage.game.GameException; import mage.game.match.MatchOptions; import mage.game.mulligan.MulliganType; import org.apache.log4j.Logger; import javax.swing.*; +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; /** * App GUI: custom options for match/tournament @@ -19,28 +28,49 @@ public class CustomOptionsDialog extends MageDialog { TABLE( PreferencesDialog.KEY_NEW_TABLE_NUMBER_OF_FREE_MULLIGANS, PreferencesDialog.KEY_NEW_TABLE_MULLIGAN_TYPE, - PreferencesDialog.KEY_NEW_TABLE_PLANECHASE + PreferencesDialog.KEY_NEW_TABLE_PLANECHASE, + PreferencesDialog.KEY_NEW_TABLE_EMBLEM_CARDS_ENABLED, + PreferencesDialog.KEY_NEW_TABLE_EMBLEM_CARDS_PER_PLAYER_FILE, + PreferencesDialog.KEY_NEW_TABLE_EMBLEM_CARDS_STARTING_PLAYER_FILE ), TOURNEY( PreferencesDialog.KEY_NEW_TOURNAMENT_NUMBER_OF_FREE_MULLIGANS, PreferencesDialog.KEY_NEW_TOURNAMENT_MULLIGUN_TYPE, - PreferencesDialog.KEY_NEW_TOURNAMENT_PLANE_CHASE + PreferencesDialog.KEY_NEW_TOURNAMENT_PLANE_CHASE, + PreferencesDialog.KEY_NEW_TOURNAMENT_EMBLEM_CARDS_ENABLED, + PreferencesDialog.KEY_NEW_TOURNAMENT_EMBLEM_CARDS_PER_PLAYER_FILE, + PreferencesDialog.KEY_NEW_TOURNAMENT_EMBLEM_CARDS_STARTING_PLAYER_FILE ); public final String NUMBER_OF_FREE_MULLIGANS; public final String MULLIGAN_TYPE; public final String PLANECHASE; + public final String EMBLEM_CARDS_ENABLED; + public final String EMBLEM_CARDS_PER_PLAYER_FILE; + public final String EMBLEM_CARDS_STARTING_PLAYER_FILE; - SaveLoadKeys(String numberOfFreeMulligans, String mulliganType, String planechase) { + SaveLoadKeys( + String numberOfFreeMulligans, + String mulliganType, + String planechase, + String emblemCardsEnabled, + String emblemCardsPerPlayerFile, + String emblemCardsStartingPlayerFile + ) { NUMBER_OF_FREE_MULLIGANS = numberOfFreeMulligans; MULLIGAN_TYPE = mulliganType; PLANECHASE = planechase; + EMBLEM_CARDS_ENABLED = emblemCardsEnabled; + EMBLEM_CARDS_PER_PLAYER_FILE = emblemCardsPerPlayerFile; + EMBLEM_CARDS_STARTING_PLAYER_FILE = emblemCardsStartingPlayerFile; } } private static final Logger logger = Logger.getLogger(CustomOptionsDialog.class); private final SaveLoadKeys saveLoadKeys; private final JButton openButton; + private final JFileChooser fcSelectEmblemCardsPerPlayer; + private final JFileChooser fcSelectEmblemCardsStartingPlayer; /** * Creates new form NewTableDialog @@ -52,6 +82,12 @@ public class CustomOptionsDialog extends MageDialog { this.spnFreeMulligans.setModel(new SpinnerNumberModel(0, 0, 5, 1)); cbMulliganType.setModel(new DefaultComboBoxModel(MulliganType.values())); this.setModal(true); + fcSelectEmblemCardsPerPlayer = new JFileChooser(); + fcSelectEmblemCardsPerPlayer.setAcceptAllFileFilterUsed(false); + fcSelectEmblemCardsPerPlayer.addChoosableFileFilter(new DeckFileFilter("dck", "XMage's deck files (*.dck)")); + fcSelectEmblemCardsStartingPlayer = new JFileChooser(); + fcSelectEmblemCardsStartingPlayer.setAcceptAllFileFilterUsed(false); + fcSelectEmblemCardsStartingPlayer.addChoosableFileFilter(new DeckFileFilter("dck", "XMage's deck files (*.dck)")); } /** @@ -71,8 +107,18 @@ public class CustomOptionsDialog extends MageDialog { jSeparator2 = new javax.swing.JSeparator(); lblVariantOptions = new javax.swing.JLabel(); chkPlaneChase = new javax.swing.JCheckBox(); + planechaseDescriptionLabel = new javax.swing.JLabel(); jSeparator4 = new javax.swing.JSeparator(); btnOK = new javax.swing.JButton(); + jSeparator3 = new javax.swing.JSeparator(); + chkEmblemCards = new javax.swing.JCheckBox(); + btnEmblemCardsPerPlayer = new javax.swing.JButton(); + txtEmblemCardsPerPlayer = new javax.swing.JTextField(); + lblEmblemCardsPerPlayer = new javax.swing.JLabel(); + btnEmblemCardsStartingPlayer = new javax.swing.JButton(); + txtEmblemCardsStartingPlayer = new javax.swing.JTextField(); + lblEmblemCardsStartingPlayer = new javax.swing.JLabel(); + emblemCardsDescriptionLabel = new javax.swing.JLabel(); setTitle("Custom Options"); @@ -103,7 +149,7 @@ public class CustomOptionsDialog extends MageDialog { lblVariantOptions.setFont(new java.awt.Font("Tahoma", 1, 11)); // NOI18N lblVariantOptions.setText("Variant Options"); - chkPlaneChase.setText("PlaneChase"); + chkPlaneChase.setText("Planechase"); chkPlaneChase.setToolTipText("Use the PlaneChase variant for your game."); chkPlaneChase.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { @@ -111,6 +157,9 @@ public class CustomOptionsDialog extends MageDialog { } }); + planechaseDescriptionLabel.setText("Shared planar deck of all implemented planes.
Uses a 9-sided planar die with 2 planeswalk sides and 2 chaos sides.
Some ability text may be incorrect.
Some rules details (such as who controls plane abilities) may be incorrect."); + planechaseDescriptionLabel.setVerticalAlignment(javax.swing.SwingConstants.TOP); + btnOK.setText("OK"); btnOK.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { @@ -118,32 +167,90 @@ public class CustomOptionsDialog extends MageDialog { } }); + chkEmblemCards.setFont(new java.awt.Font("Tahoma", 1, 11)); // NOI18N + chkEmblemCards.setText("Emblem Cards (Experimental)"); + chkEmblemCards.setToolTipText("If enabled, select cards to give players emblem copies of"); + chkEmblemCards.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkEmblemCardsActionPerformed(evt); + } + }); + + btnEmblemCardsPerPlayer.setText("..."); + btnEmblemCardsPerPlayer.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnEmblemCardsPerPlayerActionPerformed(evt); + } + }); + + txtEmblemCardsPerPlayer.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtEmblemCardsPerPlayerActionPerformed(evt); + } + }); + + lblEmblemCardsPerPlayer.setText("Per-Player File"); + lblEmblemCardsPerPlayer.setToolTipText("An emblem of each card in this file is given to each player"); + + btnEmblemCardsStartingPlayer.setText("..."); + btnEmblemCardsStartingPlayer.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnEmblemCardsStartingPlayerActionPerformed(evt); + } + }); + + txtEmblemCardsStartingPlayer.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtEmblemCardsStartingPlayerActionPerformed(evt); + } + }); + + lblEmblemCardsStartingPlayer.setText("Starting Player File"); + lblEmblemCardsStartingPlayer.setToolTipText("An emblem of every card in this file is given to the starting player (useful for symmetric effects)"); + + emblemCardsDescriptionLabel.setText("Give players emblems with the abilities of cards.
Note that some abilities may not function correctly from the command zone.
If anything breaks, please report it on GitHub."); + emblemCardsDescriptionLabel.setVerticalAlignment(javax.swing.SwingConstants.TOP); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addGroup(layout.createSequentialGroup() .addContainerGap() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(jSeparator2) - .addComponent(jSeparator4) - .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jSeparator4, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(jSeparator2, javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addGap(0, 0, Short.MAX_VALUE) + .addComponent(btnOK)) + .addComponent(jSeparator3) + .addGroup(layout.createSequentialGroup() + .addComponent(txtEmblemCardsPerPlayer) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(btnEmblemCardsPerPlayer, javax.swing.GroupLayout.PREFERRED_SIZE, 24, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(layout.createSequentialGroup() + .addComponent(txtEmblemCardsStartingPlayer) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(btnEmblemCardsStartingPlayer, javax.swing.GroupLayout.PREFERRED_SIZE, 24, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(layout.createSequentialGroup() .addComponent(lblMulliganType) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(cbMulliganType, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() + .addGroup(layout.createSequentialGroup() .addComponent(lblFreeMulligans) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(spnFreeMulligans, javax.swing.GroupLayout.DEFAULT_SIZE, 126, Short.MAX_VALUE)) - .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(chkPlaneChase) - .addComponent(lblGeneralOptions) - .addComponent(lblVariantOptions)) - .addGap(0, 125, Short.MAX_VALUE)) + .addComponent(spnFreeMulligans)) .addGroup(layout.createSequentialGroup() - .addGap(0, 0, Short.MAX_VALUE) - .addComponent(btnOK))) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(lblVariantOptions) + .addComponent(chkPlaneChase) + .addComponent(chkEmblemCards) + .addComponent(lblEmblemCardsPerPlayer) + .addComponent(lblEmblemCardsStartingPlayer) + .addComponent(lblGeneralOptions)) + .addGap(0, 0, Short.MAX_VALUE)) + .addComponent(planechaseDescriptionLabel) + .addComponent(emblemCardsDescriptionLabel)) .addContainerGap()) ); layout.setVerticalGroup( @@ -166,6 +273,26 @@ public class CustomOptionsDialog extends MageDialog { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(chkPlaneChase) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(planechaseDescriptionLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jSeparator3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(chkEmblemCards) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(emblemCardsDescriptionLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(lblEmblemCardsPerPlayer) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(txtEmblemCardsPerPlayer, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(btnEmblemCardsPerPlayer, javax.swing.GroupLayout.PREFERRED_SIZE, 21, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(lblEmblemCardsStartingPlayer) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(txtEmblemCardsStartingPlayer, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(btnEmblemCardsStartingPlayer, javax.swing.GroupLayout.PREFERRED_SIZE, 21, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(jSeparator4, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(0, 0, 0) .addComponent(btnOK, javax.swing.GroupLayout.PREFERRED_SIZE, 30, javax.swing.GroupLayout.PREFERRED_SIZE) @@ -182,6 +309,14 @@ public class CustomOptionsDialog extends MageDialog { spnFreeMulligans.getAccessibleContext().setAccessibleDescription("Select the number of free mulligans"); spnFreeMulligans.getAccessibleContext().setAccessibleParent(lblFreeMulligans); chkPlaneChase.getAccessibleContext().setAccessibleParent(lblVariantOptions); + planechaseDescriptionLabel.getAccessibleContext().setAccessibleName("Planechase Description"); + planechaseDescriptionLabel.getAccessibleContext().setAccessibleDescription("Shared planar deck of all implemented planes.\nUses a 9-sided planar die with 2 planeswalk sides and 2 chaos sides.\nSome ability text may be incorrect.\nSome rules details (such as who controls plane abilities) may be incorrect."); + planechaseDescriptionLabel.getAccessibleContext().setAccessibleParent(chkPlaneChase); + lblEmblemCardsPerPlayer.getAccessibleContext().setAccessibleParent(chkEmblemCards); + txtEmblemCardsStartingPlayer.getAccessibleContext().setAccessibleDescription(""); + lblEmblemCardsStartingPlayer.getAccessibleContext().setAccessibleParent(chkEmblemCards); + emblemCardsDescriptionLabel.getAccessibleContext().setAccessibleName("Emblem Cards description"); + emblemCardsDescriptionLabel.getAccessibleContext().setAccessibleDescription("Give players emblems with the abilities of cards.\nNote that some abilities may not function correctly from the command zone.\nIf anything breaks, please report it on GitHub."); pack(); }// //GEN-END:initComponents @@ -205,10 +340,50 @@ public class CustomOptionsDialog extends MageDialog { updateActiveCount(); }//GEN-LAST:event_chkPlaneChaseActionPerformed + private void chkEmblemCardsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_chkEmblemCardsActionPerformed + updateActiveCount(); + }//GEN-LAST:event_chkEmblemCardsActionPerformed + + private void btnEmblemCardsPerPlayerActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnEmblemCardsPerPlayerActionPerformed + loadEmblemCardFile(false); + }//GEN-LAST:event_btnEmblemCardsPerPlayerActionPerformed + + private void txtEmblemCardsPerPlayerActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_txtEmblemCardsPerPlayerActionPerformed + + }//GEN-LAST:event_txtEmblemCardsPerPlayerActionPerformed + + private void btnEmblemCardsStartingPlayerActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnEmblemCardsStartingPlayerActionPerformed + loadEmblemCardFile(true); + }//GEN-LAST:event_btnEmblemCardsStartingPlayerActionPerformed + + private void txtEmblemCardsStartingPlayerActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_txtEmblemCardsStartingPlayerActionPerformed + + }//GEN-LAST:event_txtEmblemCardsStartingPlayerActionPerformed + public void showDialog() { this.setLocation(150, 100); this.setVisible(true); } + private void loadEmblemCardFile(boolean isStartingPlayer) { + JFileChooser fileChooser = isStartingPlayer ? fcSelectEmblemCardsStartingPlayer : fcSelectEmblemCardsPerPlayer; + JTextField textField = isStartingPlayer ? txtEmblemCardsStartingPlayer : txtEmblemCardsPerPlayer; + String prefKey = isStartingPlayer ? "lastStartingPlayerEmblemCardsFolder" : "lastPerPlayerEmblemCardsFolder"; + + String lastFolder = MageFrame.getPreferences().get(prefKey, ""); + if (!lastFolder.isEmpty()) { + fileChooser.setCurrentDirectory(new File(lastFolder)); + } + int ret = fileChooser.showDialog(this, "Select Emblem Cards"); + if (ret == JFileChooser.APPROVE_OPTION) { + File file = fileChooser.getSelectedFile(); + textField.setText(file.getPath()); + try { + MageFrame.getPreferences().put(prefKey, file.getCanonicalPath()); + } catch (IOException ex) { + } + } + fileChooser.setSelectedFile(null); + } public void onLoadSettings(int version) { @@ -231,6 +406,9 @@ public class CustomOptionsDialog extends MageDialog { this.chkPlaneChase.setSelected(PreferencesDialog.getCachedValue(saveLoadKeys.PLANECHASE + versionStr, "No").equals("Yes")); this.spnFreeMulligans.setValue(Integer.parseInt(PreferencesDialog.getCachedValue(saveLoadKeys.NUMBER_OF_FREE_MULLIGANS + versionStr, "0"))); this.cbMulliganType.setSelectedItem(MulliganType.valueByName(PreferencesDialog.getCachedValue(saveLoadKeys.MULLIGAN_TYPE + versionStr, MulliganType.GAME_DEFAULT.toString()))); + this.chkEmblemCards.setSelected(PreferencesDialog.getCachedValue(saveLoadKeys.EMBLEM_CARDS_ENABLED + versionStr, "No").equals("Yes")); + this.txtEmblemCardsPerPlayer.setText(PreferencesDialog.getCachedValue(saveLoadKeys.EMBLEM_CARDS_PER_PLAYER_FILE, "")); + this.txtEmblemCardsStartingPlayer.setText(PreferencesDialog.getCachedValue(saveLoadKeys.EMBLEM_CARDS_STARTING_PLAYER_FILE, "")); updateActiveCount(); } @@ -250,6 +428,10 @@ public class CustomOptionsDialog extends MageDialog { PreferencesDialog.saveValue(saveLoadKeys.NUMBER_OF_FREE_MULLIGANS + versionStr, Integer.toString(options.getFreeMulligans())); PreferencesDialog.saveValue(saveLoadKeys.MULLIGAN_TYPE + versionStr, options.getMulliganType().toString()); PreferencesDialog.saveValue(saveLoadKeys.PLANECHASE + versionStr, options.isPlaneChase() ? "Yes" : "No"); + PreferencesDialog.saveValue(saveLoadKeys.EMBLEM_CARDS_ENABLED + versionStr, + !(options.getGlobalEmblemCards().isEmpty() && options.getPerPlayerEmblemCards().isEmpty()) ? "Yes" : "No"); + PreferencesDialog.saveValue(saveLoadKeys.EMBLEM_CARDS_PER_PLAYER_FILE + versionStr, txtEmblemCardsPerPlayer.getText()); + PreferencesDialog.saveValue(saveLoadKeys.EMBLEM_CARDS_STARTING_PLAYER_FILE + versionStr, txtEmblemCardsStartingPlayer.getText()); } /** @@ -259,6 +441,42 @@ public class CustomOptionsDialog extends MageDialog { options.setFreeMulligans((Integer) spnFreeMulligans.getValue()); options.setMullgianType((MulliganType) cbMulliganType.getSelectedItem()); options.setPlaneChase(chkPlaneChase.isSelected()); + if (chkEmblemCards.isSelected()) { + if (!txtEmblemCardsPerPlayer.getText().isEmpty()) { + Deck perPlayerEmblemDeck = null; + try { + perPlayerEmblemDeck = Deck.load(DeckImporter.importDeckFromFile(txtEmblemCardsPerPlayer.getText(), true), true, true); + } catch (GameException e1) { + JOptionPane.showMessageDialog(MageFrame.getDesktop(), e1.getMessage(), "Error loading deck", JOptionPane.ERROR_MESSAGE); + } + if (perPlayerEmblemDeck != null) { + perPlayerEmblemDeck.clearLayouts(); + options.setPerPlayerEmblemCards(perPlayerEmblemDeck.getDeckCardLists().getCards()); + } + else { + options.setPerPlayerEmblemCards(Collections.emptySet()); + } + } + if (!txtEmblemCardsStartingPlayer.getText().isEmpty()) { + Deck startingPlayerEmblemDeck = null; + try { + startingPlayerEmblemDeck = Deck.load(DeckImporter.importDeckFromFile(txtEmblemCardsStartingPlayer.getText(), true), true, true); + } catch (GameException e1) { + JOptionPane.showMessageDialog(MageFrame.getDesktop(), e1.getMessage(), "Error loading deck", JOptionPane.ERROR_MESSAGE); + } + if (startingPlayerEmblemDeck != null) { + startingPlayerEmblemDeck.clearLayouts(); + options.setGlobalEmblemCards(startingPlayerEmblemDeck.getDeckCardLists().getCards()); + } + else { + options.setGlobalEmblemCards(Collections.emptySet()); + } + } + } + else { + options.setPerPlayerEmblemCards(Collections.emptySet()); + options.setGlobalEmblemCards(Collections.emptySet()); + } } public void updateActiveCount() { @@ -266,6 +484,7 @@ public class CustomOptionsDialog extends MageDialog { if ((Integer)spnFreeMulligans.getValue() > 0) activeCount++; if (!cbMulliganType.getSelectedItem().toString().equals(MulliganType.GAME_DEFAULT.toString())) activeCount++; if (chkPlaneChase.isSelected()) activeCount++; + if (chkEmblemCards.isSelected()) activeCount++; if (activeCount == 0) { openButton.setText("Custom Options..."); } @@ -275,15 +494,25 @@ public class CustomOptionsDialog extends MageDialog { } // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton btnEmblemCardsPerPlayer; + private javax.swing.JButton btnEmblemCardsStartingPlayer; private javax.swing.JButton btnOK; private javax.swing.JComboBox cbMulliganType; + private javax.swing.JCheckBox chkEmblemCards; private javax.swing.JCheckBox chkPlaneChase; + private javax.swing.JLabel emblemCardsDescriptionLabel; private javax.swing.JSeparator jSeparator2; + private javax.swing.JSeparator jSeparator3; private javax.swing.JSeparator jSeparator4; + private javax.swing.JLabel lblEmblemCardsPerPlayer; + private javax.swing.JLabel lblEmblemCardsStartingPlayer; private javax.swing.JLabel lblFreeMulligans; private javax.swing.JLabel lblGeneralOptions; private javax.swing.JLabel lblMulliganType; private javax.swing.JLabel lblVariantOptions; + private javax.swing.JLabel planechaseDescriptionLabel; private javax.swing.JSpinner spnFreeMulligans; + private javax.swing.JTextField txtEmblemCardsPerPlayer; + private javax.swing.JTextField txtEmblemCardsStartingPlayer; // End of variables declaration//GEN-END:variables } diff --git a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java index d2666a6f1cf..91ede5a06a4 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java @@ -213,6 +213,9 @@ public class PreferencesDialog extends javax.swing.JDialog { public static final String KEY_NEW_TABLE_MINIMUM_RATING = "newTableMinimumRating"; public static final String KEY_NEW_TABLE_RATED = "newTableRated"; public static final String KEY_NEW_TABLE_EDH_POWER_LEVEL = "newTableEdhPowerLevel"; + public static final String KEY_NEW_TABLE_EMBLEM_CARDS_ENABLED = "newTableEmblemCardsEnabled"; + public static final String KEY_NEW_TABLE_EMBLEM_CARDS_PER_PLAYER_FILE = "newTableEmblemCardsPerPlayerFile"; + public static final String KEY_NEW_TABLE_EMBLEM_CARDS_STARTING_PLAYER_FILE = "newTableEmblemCardsStartingPlayerFile"; // pref setting for new tournament dialog public static final String KEY_NEW_TOURNAMENT_NAME = "newTournamentName"; @@ -237,6 +240,9 @@ public class PreferencesDialog extends javax.swing.JDialog { public static final String KEY_NEW_TOURNAMENT_QUIT_RATIO = "newTournamentQuitRatio"; public static final String KEY_NEW_TOURNAMENT_MINIMUM_RATING = "newTournamentMinimumRating"; public static final String KEY_NEW_TOURNAMENT_RATED = "newTournamentRated"; + public static final String KEY_NEW_TOURNAMENT_EMBLEM_CARDS_ENABLED = "newTournamentEmblemCardsEnabled"; + public static final String KEY_NEW_TOURNAMENT_EMBLEM_CARDS_PER_PLAYER_FILE = "newTournamentEmblemCardsPerPlayerFile"; + public static final String KEY_NEW_TOURNAMENT_EMBLEM_CARDS_STARTING_PLAYER_FILE = "newTournamentEmblemCardsStartingPlayerFile"; // Settings for auto-choosing targets public static final String KEY_AUTO_TARGET_LEVEL = "autoTargetLevel"; diff --git a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java index c1901322944..2ad921ac7aa 100644 --- a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java +++ b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java @@ -99,7 +99,8 @@ public class TablesPanel extends javax.swing.JPanel { + "
FM: = Numbers of freee mulligans" + "
Constr.: = Construction time for limited tournament formats" + "
RB = Rollback allowed" - + "
PC = Planechase active" + + "
PC = Planechase active" + + "
EC = One or more emblem cards in use" + "
SP = Spectators allowed" + "
Rng: Range of visibility for multiplayer matches" ) diff --git a/Mage.Common/src/main/java/mage/view/CardView.java b/Mage.Common/src/main/java/mage/view/CardView.java index 0112f3f13fd..6ec3569fabf 100644 --- a/Mage.Common/src/main/java/mage/view/CardView.java +++ b/Mage.Common/src/main/java/mage/view/CardView.java @@ -716,8 +716,9 @@ public class CardView extends SimpleCardView { // emblem images are always with common (black) symbol this.frameStyle = FrameStyle.M15_NORMAL; this.expansionSetCode = emblem.getExpansionSetCode(); - this.cardNumber = ""; + this.cardNumber = emblem.getCardNumber(); this.imageNumber = emblem.getImageNumber(); + this.usesVariousArt = emblem.getUsesVariousArt(); this.rarity = Rarity.COMMON; this.playableStats = emblem.playableStats.copy(); diff --git a/Mage.Common/src/main/java/mage/view/EmblemView.java b/Mage.Common/src/main/java/mage/view/EmblemView.java index 58dc7c89dbe..23b3768e967 100644 --- a/Mage.Common/src/main/java/mage/view/EmblemView.java +++ b/Mage.Common/src/main/java/mage/view/EmblemView.java @@ -1,7 +1,7 @@ package mage.view; -import mage.cards.Card; import mage.game.command.Emblem; +import mage.game.command.emblems.EmblemOfCard; import mage.players.PlayableObjectStats; import java.io.Serializable; @@ -15,7 +15,9 @@ public class EmblemView implements CommandObjectView, Serializable { protected UUID id; protected String name; + protected String cardNumber = ""; protected int imageNum; + protected boolean usesVariousArt = false; protected String expansionSetCode; protected List rules; protected PlayableObjectStats playableStats = new PlayableObjectStats(); @@ -26,6 +28,10 @@ public class EmblemView implements CommandObjectView, Serializable { this.imageNum = emblem.getImageNumber(); this.expansionSetCode = emblem.getExpansionSetCode(); this.rules = emblem.getAbilities().getRules(emblem.getName()); + if (emblem instanceof EmblemOfCard) { + cardNumber = emblem.getCardNumber(); + usesVariousArt = ((EmblemOfCard) emblem).getUsesVariousArt(); + } } @Override @@ -43,10 +49,17 @@ public class EmblemView implements CommandObjectView, Serializable { return id; } + public String getCardNumber() { + return cardNumber; + } + @Override public int getImageNumber() { return imageNum; } + public boolean getUsesVariousArt() { + return this.usesVariousArt; + } @Override public List getRules() { diff --git a/Mage.Common/src/main/java/mage/view/TableView.java b/Mage.Common/src/main/java/mage/view/TableView.java index 0c016a1e84e..a4a9c9658f0 100644 --- a/Mage.Common/src/main/java/mage/view/TableView.java +++ b/Mage.Common/src/main/java/mage/view/TableView.java @@ -117,6 +117,10 @@ public class TableView implements Serializable { if (table.getMatch().getOptions().isPlaneChase()) { addInfo.append(" PC"); } + if (!(table.getMatch().getOptions().getPerPlayerEmblemCards().isEmpty()) + || !(table.getMatch().getOptions().getGlobalEmblemCards().isEmpty())) { + addInfo.append(" EC"); + } if (table.getMatch().getOptions().isSpectatorsAllowed()) { addInfo.append(" SP"); } @@ -177,6 +181,10 @@ public class TableView implements Serializable { if (tourneyMatchOptions.isPlaneChase()) { infoText.append(" PC"); } + if (!(table.getTournament().getOptions().getMatchOptions().getPerPlayerEmblemCards().isEmpty()) + || !(table.getTournament().getOptions().getMatchOptions().getGlobalEmblemCards().isEmpty())) { + infoText.append(" EC"); + } if (table.getTournament().getOptions().isWatchingAllowed()) { infoText.append(" SP"); } diff --git a/Mage.Server/src/main/java/mage/server/TableController.java b/Mage.Server/src/main/java/mage/server/TableController.java index 3d91adbff26..7468a1950fd 100644 --- a/Mage.Server/src/main/java/mage/server/TableController.java +++ b/Mage.Server/src/main/java/mage/server/TableController.java @@ -628,6 +628,8 @@ public class TableController { gameOptions.rollbackTurnsAllowed = match.getOptions().isRollbackTurnsAllowed(); gameOptions.bannedUsers = match.getOptions().getBannedUsers(); gameOptions.planeChase = match.getOptions().isPlaneChase(); + gameOptions.perPlayerEmblemCards = match.getOptions().getPerPlayerEmblemCards(); + gameOptions.globalEmblemCards = match.getOptions().getGlobalEmblemCards(); match.getGame().setGameOptions(gameOptions); managerFactory.gameManager().createGameSession(match.getGame(), userPlayerMap, table.getId(), choosingPlayerId, gameOptions); String creator = null; diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/emblems/EmblemOfCardTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/emblems/EmblemOfCardTest.java new file mode 100644 index 00000000000..e480a360f99 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/emblems/EmblemOfCardTest.java @@ -0,0 +1,184 @@ +package org.mage.test.cards.emblems; + +import mage.cards.repository.CardRepository; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.game.command.emblems.EmblemOfCard; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author artemiswkearney + */ +public class EmblemOfCardTest extends CardTestPlayerBase { + + @Test + public void testEmblemOfGriselbrand() { + // Flying, lifelink + // Pay 7 life: Draw seven cards. + addEmblem(playerA, new EmblemOfCard( + CardRepository.instance.findCard("Griselbrand", true).getMockCard() + )); + + setLife(playerA, 20); + + assertHandCount(playerA, 0); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pay 7 life: Draw"); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertHandCount(playerA, 7); + assertLife(playerA, 13); + assertEmblemCount(playerA, 1); + } + @Test + public void testEmblemOfYurlok() { + // Vigilance + // A player losing unspent mana causes that player to lose that much life. + // {1}, {T}: Each player adds {B}{R}{G}. + addEmblem(playerA, new EmblemOfCard( + CardRepository.instance.findCard("Yurlok of Scorch Thrash", true).getMockCard() + )); + + setLife(playerA, 20); + + // {T}: Add {R}. + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); + + checkManaPool("after tapping Mountain", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "R", 1); + checkPlayableAbility("can't tap emblem", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}, {T}:", false); + + // wait for mana burn + setStopAt(1, PhaseStep.BEGIN_COMBAT); + + checkLife("takes 1 point of mana burn", 1, PhaseStep.BEGIN_COMBAT, playerA, 19); + execute(); + + assertEmblemCount(playerA, 1); + } + + @Test + public void testEmblemOfOmniscience() { + // You may cast spells from your hand without paying their mana costs. + addEmblem(playerA, new EmblemOfCard( + CardRepository.instance.findCard("Omniscience", true).getMockCard() + )); + + // Colossal Dreadmaw {4}{G}{G} + // Creature - Dinosaur 6/6 + // Trample + addCard(Zone.HAND, playerA, "Colossal Dreadmaw"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Colossal Dreadmaw"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + assertPermanentCount(playerA, "Colossal Dreadmaw", 1); + assertEmblemCount(playerA, 1); + } + @Test + public void testEmblemOfParadoxEngine() { + // Whenever you cast a spell, untap all nonland permanents you control. + addEmblem(playerA, new EmblemOfCard( + CardRepository.instance.findCard("Paradox Engine", true).getMockCard() + )); + + // {T}: Add {G}. + addCard(Zone.BATTLEFIELD, playerA, "Mox Emerald"); + + // Sol Ring {1} + // Artifact + // {T}: Add {C}{C}. + addCard(Zone.HAND, playerA, "Sol Ring"); + + // Basalt Monolith {3} + // Artifact + // Basalt Monolith doesn’t untap during your untap step. + // {T}: Add {C}{C}{C}. + // {3}: Untap Basalt Monolith. + addCard(Zone.HAND, playerA, "Basalt Monolith"); + + // Book of Rass {6} + // Artifact + // {2}, Pay 2 life: Draw a card. + // (just a dummy artifact to cast and spend the mana with) + addCard(Zone.HAND, playerA, "Book of Rass"); + + setLife(playerA, 20); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sol Ring"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Basalt Monolith"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Book of Rass"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}, Pay"); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}, Pay"); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}, Pay"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertLife(playerA, 14); + assertEmblemCount(playerA, 1); + } + @Test + public void testEmblemOfDoublingSeason() { + // If an effect would create one or more tokens under your control, it + // creates twice that many of those tokens instead. + // If an effect would put one or more counters on a permanent you + // control, it puts twice that many of those counters on that permanent instead. + addEmblem(playerA, new EmblemOfCard( + CardRepository.instance.findCard("Doubling Season", true).getMockCard() + )); + + // {T}: Add {W}. + addCard(Zone.BATTLEFIELD, playerA, "Plains", 6); + + // Elspeth, Sun's Champion {4}{W}{W} + // Legendary Planeswalker — Elspeth + // +1: Create three 1/1 white Soldier creature tokens. + // −3: Destroy all creatures with power 4 or greater. + // −7: You get an emblem with “Creatures you control get +2/+2 and have flying.” + // Loyalty: 4 + addCard(Zone.HAND, playerA, "Elspeth, Sun's Champion"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Elspeth, Sun's Champion"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCounters( + "Elspeth's loyalty is doubled", + 1, + PhaseStep.PRECOMBAT_MAIN, + playerA, + "Elspeth, Sun's Champion", + CounterType.LOYALTY, + 8 + ); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Create"); + checkPlayableAbility("can't still activate Griselbrand", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pay 7 life:", false); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCounters( + "+1 is not doubled", + 1, + PhaseStep.PRECOMBAT_MAIN, + playerA, + "Elspeth, Sun's Champion", + CounterType.LOYALTY, + 9 + ); + checkPermanentCount( + "Soldier tokens doubled", + 1, + PhaseStep.PRECOMBAT_MAIN, + playerA, + "Soldier Token", + 6 + ); + execute(); + assertEmblemCount(playerA, 1); + } +} diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index 3d0a830b964..f68a131b98a 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -1481,6 +1481,13 @@ public abstract class AbilityImpl implements Ability { } public AbilityImpl copyWithZone(Zone zone) { + if (this instanceof MageSingleton) { + // not safe to change zone for singletons + // in theory there could be some sort of wrapper to effectively change + // the zone here, but currently no use of copyWithZone actually needs + // to change the zone of any existing singleton abilities + return this; + } AbilityImpl copy = ((AbilityImpl)this.copy()); copy.zone = zone; copy.newId(); diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 17cb7312a11..275f3974007 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -22,6 +22,7 @@ import mage.abilities.mana.TriggeredManaAbility; import mage.actions.impl.MageAction; import mage.cards.*; import mage.cards.decks.Deck; +import mage.cards.decks.DeckCardInfo; import mage.choices.Choice; import mage.constants.*; import mage.counters.CounterType; @@ -41,6 +42,7 @@ import mage.game.combat.Combat; import mage.game.combat.CombatGroup; import mage.game.command.*; import mage.game.command.dungeons.UndercityDungeon; +import mage.game.command.emblems.EmblemOfCard; import mage.game.command.emblems.TheRingEmblem; import mage.game.events.*; import mage.game.events.TableEvent.EventType; @@ -1317,6 +1319,22 @@ public abstract class GameImpl implements Game { addPlane(plane, startingPlayerId); state.setPlaneChase(this, gameOptions.planeChase); } + + if (!gameOptions.perPlayerEmblemCards.isEmpty()) { + for (UUID playerId : state.getPlayerList(startingPlayerId)) { + for (DeckCardInfo info : gameOptions.perPlayerEmblemCards) { + Card card = EmblemOfCard.cardFromDeckInfo(info); + addEmblem(new EmblemOfCard(card), card, playerId); + } + } + } + + if (!gameOptions.globalEmblemCards.isEmpty()) { + for (DeckCardInfo info : gameOptions.globalEmblemCards) { + Card card = EmblemOfCard.cardFromDeckInfo(info); + addEmblem(new EmblemOfCard(card), card, startingPlayerId); + } + } } public void initGameDefaultWatchers() { diff --git a/Mage/src/main/java/mage/game/GameOptions.java b/Mage/src/main/java/mage/game/GameOptions.java index 918b05fd6c9..f9fe54a7727 100644 --- a/Mage/src/main/java/mage/game/GameOptions.java +++ b/Mage/src/main/java/mage/game/GameOptions.java @@ -1,10 +1,13 @@ package mage.game; +import mage.cards.decks.DeckCardInfo; import mage.constants.PhaseStep; import mage.util.Copyable; import java.io.Serializable; +import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.Set; /** @@ -52,6 +55,15 @@ public class GameOptions implements Serializable, Copyable { */ public Set bannedUsers = Collections.emptySet(); + /** + * Cards to be given to each player as emblems + */ + public Collection perPlayerEmblemCards = Collections.emptySet(); + /** + * Cards to be given to the starting player as emblems + */ + public Collection globalEmblemCards = Collections.emptySet(); + // PLANECHASE game mode public boolean planeChase = false; @@ -73,6 +85,8 @@ public class GameOptions implements Serializable, Copyable { this.rollbackTurnsAllowed = options.rollbackTurnsAllowed; this.bannedUsers.addAll(options.bannedUsers); this.planeChase = options.planeChase; + this.perPlayerEmblemCards = new HashSet<>(options.perPlayerEmblemCards); + this.globalEmblemCards = new HashSet<>(options.globalEmblemCards); } @Override diff --git a/Mage/src/main/java/mage/game/command/Emblem.java b/Mage/src/main/java/mage/game/command/Emblem.java index 7ca863fd12c..b34bb6ba2f7 100644 --- a/Mage/src/main/java/mage/game/command/Emblem.java +++ b/Mage/src/main/java/mage/game/command/Emblem.java @@ -33,7 +33,7 @@ public abstract class Emblem extends CommandObjectImpl { private static final ManaCosts emptyCost = new ManaCostsImpl<>(); private UUID controllerId; - private MageObject sourceObject; + protected MageObject sourceObject; private boolean copy; private MageObject copyFrom; // copied card INFO (used to call original adjusters) private FrameStyle frameStyle; diff --git a/Mage/src/main/java/mage/game/command/emblems/EmblemOfCard.java b/Mage/src/main/java/mage/game/command/emblems/EmblemOfCard.java new file mode 100644 index 00000000000..74c16dbb2e2 --- /dev/null +++ b/Mage/src/main/java/mage/game/command/emblems/EmblemOfCard.java @@ -0,0 +1,108 @@ +package mage.game.command.emblems; + +import mage.MageObject; +import mage.abilities.AbilityImpl; +import mage.cards.Card; +import mage.cards.decks.DeckCardInfo; +import mage.cards.mock.MockCard; +import mage.cards.repository.CardCriteria; +import mage.cards.repository.CardInfo; +import mage.cards.repository.CardRepository; +import mage.constants.Zone; +import mage.game.command.Emblem; +import mage.util.CardUtil; +import org.apache.log4j.Logger; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author artemiswkearney + * Emblem with all the abilities of an existing card. + * Can be used for custom gamemodes like Omniscience Draft (as seen on Arena), + * mana burn with Yurlok of Scorch Thrash, and anything else players might think of. + */ +public final class EmblemOfCard extends Emblem { + private final boolean usesVariousArt; + private static final Logger logger = Logger.getLogger(EmblemOfCard.class); + + public static Card lookupCard( + String cardName, + String cardNumber, + String setCode, + String infoTypeForError + ) { + int cardNumberInt = CardUtil.parseCardNumberAsInt(cardNumber); + List found = CardRepository.instance.findCards(new CardCriteria() + .name(cardName) + .minCardNumber(cardNumberInt) + .maxCardNumber(cardNumberInt) + .setCodes(setCode)); + return found.stream() + .filter(ci -> ci.getCardNumber().equals(cardNumber)) + .findFirst() + .orElseGet(() -> found.stream() + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("No real card for " + infoTypeForError + " " + cardName))) + .getCard(); + } + + public static Card cardFromDeckInfo(DeckCardInfo info) { + return lookupCard( + info.getCardName(), + info.getCardNum(), + info.getSetCode(), + "DeckCardInfo" + ); + } + + public EmblemOfCard(Card card, Zone zone) { + super(card.getName()); + if (card instanceof MockCard) { + card = lookupCard( + card.getName(), + card.getCardNumber(), + card.getExpansionSetCode(), + "MockCard" + ); + } + this.getAbilities().addAll(card.getAbilities().stream().filter( + ability -> zone.match(ability.getZone()) + ).map(ability -> { + if (ability instanceof AbilityImpl && ability.getZone() == zone) { + return ((AbilityImpl)ability).copyWithZone(Zone.COMMAND); + } + return ability; + }).collect(Collectors.toList())); + this.getAbilities().setSourceId(this.getId()); + this.setExpansionSetCode(card.getExpansionSetCode()); + this.setCardNumber(card.getCardNumber()); + this.setImageNumber(card.getImageNumber()); + this.usesVariousArt = card.getUsesVariousArt(); + } + + public EmblemOfCard(Card card) { + this(card, Zone.BATTLEFIELD); + } + + private EmblemOfCard(EmblemOfCard eoc) { + super(eoc); + this.usesVariousArt = eoc.usesVariousArt; + } + @Override + public EmblemOfCard copy() { + return new EmblemOfCard(this); + } + + @Override + public void setSourceObject(MageObject sourceObject) { + this.sourceObject = sourceObject; + // super method would try and fail to find the emblem image here + // (not sure why that would be setSoureObject's job; we get our image during construction) + } + + public boolean getUsesVariousArt() { + return usesVariousArt; + } +} + diff --git a/Mage/src/main/java/mage/game/match/MatchOptions.java b/Mage/src/main/java/mage/game/match/MatchOptions.java index dc0dfb0eee6..16047fc41a3 100644 --- a/Mage/src/main/java/mage/game/match/MatchOptions.java +++ b/Mage/src/main/java/mage/game/match/MatchOptions.java @@ -1,6 +1,7 @@ package mage.game.match; +import mage.cards.decks.DeckCardInfo; import mage.constants.MatchBufferTime; import mage.constants.MatchTimeLimit; import mage.constants.MultiplayerAttackOption; @@ -11,10 +12,7 @@ import mage.game.result.ResultProtos; import mage.players.PlayerType; import java.io.Serializable; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; /** * @@ -52,6 +50,9 @@ public class MatchOptions implements Serializable { protected MatchBufferTime matchBufferTime; // Amount of time each player gets before their normal time limit counts down. Refreshes each time the normal timer is invoked. protected MulliganType mulliganType; + protected Collection perPlayerEmblemCards; + protected Collection globalEmblemCards; + /*public MatchOptions(String name, String gameType) { this.name = name; this.gameType = gameType; @@ -65,6 +66,8 @@ public class MatchOptions implements Serializable { this.password = ""; this.multiPlayer = multiPlayer; this.numSeats = numSeats; + this.perPlayerEmblemCards = Collections.emptySet(); + this.globalEmblemCards = Collections.emptySet(); } public void setNumSeats (int numSeats) { @@ -288,4 +291,19 @@ public class MatchOptions implements Serializable { return mulliganType; } + public Collection getPerPlayerEmblemCards() { + return perPlayerEmblemCards; + } + + public void setPerPlayerEmblemCards(Collection perPlayerEmblemCards) { + this.perPlayerEmblemCards = perPlayerEmblemCards; + } + + public Collection getGlobalEmblemCards() { + return globalEmblemCards; + } + + public void setGlobalEmblemCards(Collection globalEmblemCards) { + this.globalEmblemCards = globalEmblemCards; + } }