diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.form b/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.form index 8fa93c55b07..478bd33dc4a 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.form +++ b/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.form @@ -204,6 +204,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java b/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java index 90249dc9153..a8ecb466225 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java @@ -47,10 +47,12 @@ import mage.cards.Sets; import mage.cards.repository.CardCriteria; import mage.cards.repository.CardInfo; import mage.cards.repository.CardRepository; +import mage.choices.ChoiceImpl; import mage.client.MageFrame; import mage.client.cards.*; import mage.client.constants.Constants.SortBy; import mage.client.deckeditor.table.TableModel; +import mage.client.dialog.PickChoiceDialog; import mage.client.util.GUISizeHelper; import mage.client.util.sets.ConstructedFormats; import mage.constants.CardType; @@ -206,6 +208,7 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene this.btnBooster.setVisible(false); this.btnClear.setVisible(false); this.cbExpansionSet.setVisible(false); + this.btnExpansionSearch.setVisible(false); this.limited = true; this.cards.clear(); for (Card card : sideboard) { @@ -219,10 +222,44 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene this.btnBooster.setVisible(true); this.btnClear.setVisible(true); this.cbExpansionSet.setVisible(true); + this.btnExpansionSearch.setVisible(true); // cbExpansionSet.setModel(new DefaultComboBoxModel<>(ConstructedFormats.getTypes())); // Action event on Expansion set triggers loadCards method cbExpansionSet.setSelectedIndex(0); } + + public void doFastExpansionSearch(){ + mage.choices.Choice choice = new ChoiceImpl(false); + + // collect data from expansion combobox (String) + DefaultComboBoxModel comboModel = (DefaultComboBoxModel)cbExpansionSet.getModel(); + Map choiceItems = new HashMap<>(comboModel.getSize()); + Map choiceSorting = new HashMap<>(comboModel.getSize()); + String item; + + for(int i = 0; i < comboModel.getSize() - 1; i++){ + item = (String)comboModel.getElementAt(i); + choiceItems.put(item, item); + choiceSorting.put(item, i); // need so sorting + } + + choice.setKeyChoices(choiceItems); + choice.setSortData(choiceSorting); + choice.setMessage("Select set or expansion"); + + // current selection value restore + String needSelectValue; + needSelectValue = (String)comboModel.getSelectedItem(); + + // ask for new value + PickChoiceDialog dlg = new PickChoiceDialog(); + dlg.setWindowSize(300, 500); + dlg.showDialog(choice, needSelectValue); + if(choice.isChosen()){ + item = choice.getChoiceKey(); + comboModel.setSelectedItem(item); + } + } private FilterCard buildFilter() { FilterCard filter = new FilterCard(); @@ -471,6 +508,7 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene tbColorless = new javax.swing.JToggleButton(); jSeparator1 = new javax.swing.JToolBar.Separator(); cbExpansionSet = new javax.swing.JComboBox<>(); + btnExpansionSearch = new javax.swing.JButton(); jSeparator2 = new javax.swing.JToolBar.Separator(); chkPennyDreadful = new javax.swing.JCheckBox(); btnBooster = new javax.swing.JButton(); @@ -615,10 +653,23 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene } }); tbColor.add(cbExpansionSet); + + btnExpansionSearch.setIcon(new javax.swing.ImageIcon(getClass().getResource("/buttons/search_32.png"))); // NOI18N + btnExpansionSearch.setToolTipText("Fast search set or expansion"); + btnExpansionSearch.setAlignmentX(1.0F); + btnExpansionSearch.setFocusable(false); + btnExpansionSearch.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + btnExpansionSearch.setPreferredSize(new java.awt.Dimension(23, 23)); + btnExpansionSearch.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); + btnExpansionSearch.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnExpansionSearchActionPerformed(evt); + } + }); + tbColor.add(btnExpansionSearch); tbColor.add(jSeparator2); - - chkPennyDreadful.setText("Penny Dreadful"); + chkPennyDreadful.setText("Penny Dreadful Only"); chkPennyDreadful.setToolTipText("Will only allow Penny Dreadful legal cards to be shown."); chkPennyDreadful.setFocusable(false); chkPennyDreadful.setHorizontalTextPosition(javax.swing.SwingConstants.RIGHT); @@ -628,15 +679,7 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene chkPilesActionPerformed(evt); } }); - - JPopupMenu filterByFormatPopup = new JPopupMenu(); - filterByFormatPopup.add(chkPennyDreadful); - filterByFormatPopup.setLayout(new GridBagLayout()); - - ButtonGroup selectByTypeModeGroup = new ButtonGroup(); - JButton filterByFormatButton = new JButton ("Filter by Format"); - makeButtonPopup(filterByFormatButton, filterByFormatPopup); - tbColor.add(filterByFormatButton); + tbColor.add(chkPennyDreadful); btnBooster.setText("Open Booster"); btnBooster.setToolTipText("(CURRENTLY NOT WORKING) Generates a booster of the selected set and adds the cards to the card selector."); @@ -1211,6 +1254,10 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene // TODO add your handling code here: }//GEN-LAST:event_chkRulesActionPerformed + private void btnExpansionSearchActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnExpansionSearchActionPerformed + doFastExpansionSearch(); + }//GEN-LAST:event_btnExpansionSearchActionPerformed + private void toggleViewMode() { if (currentView instanceof CardGrid) { jToggleListView.setSelected(true); @@ -1253,6 +1300,7 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene private javax.swing.ButtonGroup bgView; private javax.swing.JButton btnBooster; private javax.swing.JButton btnClear; + private javax.swing.JButton btnExpansionSearch; private javax.swing.JLabel cardCount; private javax.swing.JLabel cardCountLabel; private javax.swing.JPanel cardSelectorBottomPanel; diff --git a/Mage.Client/src/main/java/mage/client/dialog/AboutDialog.java b/Mage.Client/src/main/java/mage/client/dialog/AboutDialog.java index 2722fe681a1..0449487c114 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/AboutDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/AboutDialog.java @@ -85,9 +85,8 @@ public class AboutDialog extends MageDialog { jLabel2.setText("Courtesy: BetaSteward@googlemail.com. Site: http://XMage.de/"); - jLabel3.setText("Devs: BetaSteward, Noxx, Eugen.Rivniy, North, LevelX2, Jeff, Plopman, dustinconrad, emerald000.,"); - - jLabel4.setText("fireshoes, lunaskyrise, mnapoleon, jgod, LoneFox, drmDev, spjspj."); + jLabel3.setText("Devs: BetaSteward, Noxx, Eugen.Rivniy, North, LevelX2, Jeff, Plopman, dustinconrad, emerald000,"); + jLabel4.setText("fireshoes, lunaskyrise, mnapoleon, jgod, LoneFox, drmDev, spjspj, L_J, JayDi85."); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); diff --git a/Mage.Client/src/main/java/mage/client/dialog/ConnectDialog.form b/Mage.Client/src/main/java/mage/client/dialog/ConnectDialog.form index a46c8a317ab..1202ae50beb 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/ConnectDialog.form +++ b/Mage.Client/src/main/java/mage/client/dialog/ConnectDialog.form @@ -26,68 +26,76 @@ - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -101,39 +109,42 @@ + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + @@ -200,11 +211,6 @@ - - - - - @@ -233,7 +239,13 @@ - + + + + + + + @@ -242,6 +254,10 @@ + + + + @@ -251,8 +267,11 @@ - + + + + @@ -260,8 +279,11 @@ - + + + + @@ -269,12 +291,16 @@ + + + - + - - + + + @@ -293,11 +319,13 @@ - + - - + + + + @@ -309,12 +337,16 @@ + + + - + - - + + + @@ -324,5 +356,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Mage.Client/src/main/java/mage/client/dialog/ConnectDialog.java b/Mage.Client/src/main/java/mage/client/dialog/ConnectDialog.java index b9ac1834ec1..d0b946fa0cb 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/ConnectDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/ConnectDialog.java @@ -51,14 +51,21 @@ import java.net.URL; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import javax.swing.DefaultComboBoxModel; import javax.swing.JLayeredPane; import javax.swing.JOptionPane; import javax.swing.SwingWorker; +import mage.choices.Choice; +import mage.choices.ChoiceImpl; import mage.client.MageFrame; import static mage.client.dialog.PreferencesDialog.KEY_CONNECTION_URL_SERVER_LIST; import static mage.client.dialog.PreferencesDialog.KEY_CONNECT_AUTO_CONNECT; @@ -152,7 +159,6 @@ public class ConnectDialog extends MageDialog { lblPassword = new javax.swing.JLabel(); txtPassword = new javax.swing.JPasswordField(); lblFlag = new javax.swing.JLabel(); - cbFlag = new mage.client.util.gui.countryBox.CountryComboBox(); chkAutoConnect = new javax.swing.JCheckBox(); chkForceUpdateDB = new javax.swing.JCheckBox(); jProxySettingsButton = new javax.swing.JButton(); @@ -164,6 +170,11 @@ public class ConnectDialog extends MageDialog { btnFind1 = new javax.swing.JButton(); btnFind2 = new javax.swing.JButton(); btnFind3 = new javax.swing.JButton(); + lblFastConnect = new javax.swing.JLabel(); + panelFlag = new javax.swing.JPanel(); + cbFlag = new mage.client.util.gui.countryBox.CountryComboBox(); + filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(5, 0), new java.awt.Dimension(4, 0), new java.awt.Dimension(5, 32767)); + btnFlagSearch = new javax.swing.JButton(); setTitle("Connect to server"); setNormalBounds(new java.awt.Rectangle(100, 100, 410, 307)); @@ -174,7 +185,11 @@ public class ConnectDialog extends MageDialog { btnFind.setText("Find..."); btnFind.setToolTipText("Shows the list of public servers"); btnFind.setName("findServerBtn"); // NOI18N - btnFind.addActionListener(evt -> findPublicServerActionPerformed(evt)); + btnFind.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + findPublicServerActionPerformed(evt); + } + }); lblPort.setLabelFor(txtPort); lblPort.setText("Port:"); @@ -194,119 +209,194 @@ public class ConnectDialog extends MageDialog { lblFlag.setLabelFor(txtUserName); lblFlag.setText("User flag:"); - cbFlag.setEditable(true); - chkAutoConnect.setText("Automatically connect to this server next time"); chkAutoConnect.setToolTipText("If active this connect dialog will not be shown if you choose to connect.
\nInstead XMage tries to connect to the last server you were connected to."); - chkAutoConnect.addActionListener(evt -> chkAutoConnectActionPerformed(evt)); + chkAutoConnect.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkAutoConnectActionPerformed(evt); + } + }); chkForceUpdateDB.setText("Force update of card database"); chkForceUpdateDB.setToolTipText("If active the comparison of the server cards database to the client database will be enforced.
If not, the comparison will only done if the database version of the client is lower than the version of the server."); - chkForceUpdateDB.addActionListener(evt -> chkForceUpdateDBActionPerformed(evt)); + chkForceUpdateDB.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkForceUpdateDBActionPerformed(evt); + } + }); jProxySettingsButton.setText("Proxy Settings..."); - jProxySettingsButton.addActionListener(evt -> jProxySettingsButtonActionPerformed(evt)); + jProxySettingsButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jProxySettingsButtonActionPerformed(evt); + } + }); - btnConnect.setText("Connect"); - btnConnect.addActionListener(evt -> btnConnectActionPerformed(evt)); + btnConnect.setFont(new java.awt.Font("Tahoma", 1, 11)); // NOI18N + btnConnect.setText("Connect to server"); + btnConnect.setMargin(new java.awt.Insets(2, 2, 2, 2)); + btnConnect.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnConnectActionPerformed(evt); + } + }); btnCancel.setText("Cancel"); - btnCancel.addActionListener(evt -> btnCancelActionPerformed(evt)); + btnCancel.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + btnCancel.setMargin(new java.awt.Insets(2, 2, 2, 2)); + btnCancel.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnCancelActionPerformed(evt); + } + }); - btnRegister.setText("Register new user"); + btnRegister.setText("Register new user..."); btnRegister.setToolTipText("XMage now supports user authentication.
Register your account before you log in."); - btnRegister.addActionListener(evt -> btnRegisterActionPerformed(evt)); + btnRegister.setMargin(new java.awt.Insets(2, 2, 2, 2)); + btnRegister.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnRegisterActionPerformed(evt); + } + }); - btnForgotPassword.setText("Forgot password"); + btnForgotPassword.setText("Forgot password..."); btnForgotPassword.setToolTipText("You can reset your password if you have registered
your account with an email address."); - btnForgotPassword.addActionListener(evt -> btnForgotPasswordActionPerformed(evt)); + btnForgotPassword.setMargin(new java.awt.Insets(2, 2, 2, 2)); + btnForgotPassword.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnForgotPasswordActionPerformed(evt); + } + }); + btnFind1.setIcon(new javax.swing.ImageIcon(getClass().getResource("/flags/de.png"))); // NOI18N btnFind1.setText("X"); - btnFind1.setToolTipText("Connect to xmage.de"); + btnFind1.setToolTipText("Connect to xmage.de (Europe, most popular)"); btnFind1.setActionCommand("connectXmageDe"); btnFind1.setAlignmentY(0.0F); - btnFind1.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); - btnFind1.setHorizontalTextPosition(javax.swing.SwingConstants.LEFT); + btnFind1.setMargin(new java.awt.Insets(2, 2, 2, 2)); btnFind1.setMaximumSize(new java.awt.Dimension(42, 23)); btnFind1.setMinimumSize(new java.awt.Dimension(42, 23)); btnFind1.setName("connectXmageDeBtn"); // NOI18N btnFind1.setPreferredSize(new java.awt.Dimension(23, 23)); - btnFind1.addActionListener(evt -> connectXmageDe(evt)); + btnFind1.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + connectXmageDe(evt); + } + }); btnFind2.setText("L"); - btnFind2.setToolTipText("Connect to localhost"); + btnFind2.setToolTipText("Connect to localhost (local server)"); btnFind2.setActionCommand("connectLocalhost"); btnFind2.setAlignmentY(0.0F); - btnFind2.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); - btnFind2.setHorizontalTextPosition(javax.swing.SwingConstants.LEFT); + btnFind2.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + btnFind2.setMargin(new java.awt.Insets(2, 2, 2, 2)); btnFind2.setName("connectLocalhostBtn"); // NOI18N btnFind2.setPreferredSize(new java.awt.Dimension(23, 23)); - btnFind2.addActionListener(evt -> connectLocalhost(evt)); + btnFind2.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + connectLocalhost(evt); + } + }); + btnFind3.setIcon(new javax.swing.ImageIcon(getClass().getResource("/flags/us.png"))); // NOI18N btnFind3.setText("W"); - btnFind3.setToolTipText("Connect to woogerworks"); + btnFind3.setToolTipText("Connect to Woogerworks (USA)"); btnFind3.setActionCommand("connectWoogerworks"); btnFind3.setAlignmentY(0.0F); - btnFind3.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); - btnFind3.setHorizontalTextPosition(javax.swing.SwingConstants.LEFT); + btnFind3.setMargin(new java.awt.Insets(2, 2, 2, 2)); btnFind3.setName("connectWoogerworksBtn"); // NOI18N btnFind3.setPreferredSize(new java.awt.Dimension(23, 23)); - btnFind3.addActionListener(evt -> connectWoogerworks(evt)); + btnFind3.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + connectWoogerworks(evt); + } + }); + + lblFastConnect.setLabelFor(btnFind1); + lblFastConnect.setText("Fast connect to:"); + lblFastConnect.setName(""); // NOI18N + + panelFlag.setPreferredSize(new java.awt.Dimension(189, 30)); + panelFlag.setLayout(new javax.swing.BoxLayout(panelFlag, javax.swing.BoxLayout.LINE_AXIS)); + + cbFlag.setEditable(true); + cbFlag.setMaximumRowCount(16); + cbFlag.setAlignmentX(0.0F); + cbFlag.setMinimumSize(new java.awt.Dimension(50, 18)); + cbFlag.setPreferredSize(new java.awt.Dimension(278, 15)); + panelFlag.add(cbFlag); + panelFlag.add(filler1); + + btnFlagSearch.setIcon(new javax.swing.ImageIcon(getClass().getResource("/buttons/search_24.png"))); // NOI18N + btnFlagSearch.setToolTipText("Fast search your flag"); + btnFlagSearch.setAlignmentX(1.0F); + btnFlagSearch.setPreferredSize(new java.awt.Dimension(23, 23)); + btnFlagSearch.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnFlagSearchActionPerformed(evt); + } + }); + panelFlag.add(btnFlagSearch); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(29, 29, 29) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) .addComponent(lblPort) - .addComponent(lblServer) - .addComponent(lblUserName) - .addComponent(lblPassword)) - .addComponent(lblFlag, javax.swing.GroupLayout.Alignment.TRAILING)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(lblServer))) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addContainerGap() + .addComponent(lblFlag))) + .addGroup(layout.createSequentialGroup() + .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(lblStatus, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(chkForceUpdateDB, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(chkAutoConnect, javax.swing.GroupLayout.DEFAULT_SIZE, 386, Short.MAX_VALUE) - .addComponent(jProxySettingsButton) - .addComponent(cbFlag, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addComponent(txtServer, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 286, Short.MAX_VALUE) - .addComponent(txtUserName, javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(txtPassword, javax.swing.GroupLayout.Alignment.TRAILING) - .addGroup(layout.createSequentialGroup() - .addComponent(txtPort, javax.swing.GroupLayout.PREFERRED_SIZE, 71, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(btnFind1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(btnFind3, javax.swing.GroupLayout.PREFERRED_SIZE, 42, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(btnFind2, javax.swing.GroupLayout.PREFERRED_SIZE, 42, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(69, 69, 69))) - .addGap(8, 8, 8) - .addComponent(btnFind, javax.swing.GroupLayout.DEFAULT_SIZE, 92, Short.MAX_VALUE)))) + .addComponent(lblUserName) + .addComponent(lblPassword, javax.swing.GroupLayout.Alignment.TRAILING)))) + .addGap(0, 0, 0) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(btnConnect, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(btnRegister, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(btnForgotPassword, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(btnCancel, javax.swing.GroupLayout.PREFERRED_SIZE, 77, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(lblStatus, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(chkForceUpdateDB, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(jProxySettingsButton) + .addGap(0, 0, Short.MAX_VALUE)) + .addComponent(chkAutoConnect, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addGap(0, 0, Short.MAX_VALUE) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(btnRegister) + .addComponent(panelFlag, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(txtServer, javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(txtUserName, javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(txtPassword, javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addComponent(btnConnect) + .addComponent(txtPort, javax.swing.GroupLayout.PREFERRED_SIZE, 71, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 11, Short.MAX_VALUE) + .addComponent(lblFastConnect) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(btnForgotPassword) + .addComponent(btnFind1, javax.swing.GroupLayout.PREFERRED_SIZE, 70, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(btnCancel))) - .addGap(26, 26, 26))) + .addComponent(btnFind3, javax.swing.GroupLayout.PREFERRED_SIZE, 70, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(btnFind2, javax.swing.GroupLayout.PREFERRED_SIZE, 40, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addGap(0, 0, 0) + .addComponent(btnFind))) .addContainerGap()) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(lblServer) @@ -318,35 +408,37 @@ public class ConnectDialog extends MageDialog { .addComponent(lblPort) .addComponent(btnFind1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(btnFind2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(btnFind3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(btnFind3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(lblFastConnect)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(txtUserName, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(lblUserName)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(txtPassword, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(lblPassword)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addComponent(lblFlag, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(cbFlag, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addGap(5, 5, 5) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(panelFlag, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(lblFlag, javax.swing.GroupLayout.PREFERRED_SIZE, 18, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(chkAutoConnect) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(chkForceUpdateDB) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(jProxySettingsButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 47, Short.MAX_VALUE) - .addComponent(lblStatus, javax.swing.GroupLayout.PREFERRED_SIZE, 24, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(btnConnect) - .addComponent(btnCancel) - .addComponent(btnForgotPassword)) - .addGap(3, 3, 3) - .addComponent(btnRegister) - .addContainerGap()) + .addComponent(lblStatus, javax.swing.GroupLayout.PREFERRED_SIZE, 24, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) + .addGroup(layout.createSequentialGroup() + .addComponent(btnRegister, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(btnForgotPassword, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addComponent(btnConnect, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(btnCancel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGap(23, 23, 23)) ); pack(); @@ -640,6 +732,45 @@ public class ConnectDialog extends MageDialog { this.txtPassword.setText(MagePreferences.getPassword(serverAddress)); }//GEN-LAST:event_connectWoogerworks + private void btnFlagSearchActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnFlagSearchActionPerformed + doFastFlagSearch(); + }//GEN-LAST:event_btnFlagSearchActionPerformed + + private void doFastFlagSearch(){ + Choice choice = new ChoiceImpl(false); + + // collect data from country combobox String[name][code] + Map choiceItems = new LinkedHashMap<>(); + DefaultComboBoxModel flagModel = (DefaultComboBoxModel)cbFlag.getModel(); + String[] flagItem; + + for(int i = 0; i < flagModel.getSize() - 1; i++){ + flagItem = (String[])flagModel.getElementAt(i); + choiceItems.put(flagItem[1], flagItem[0]); + } + + choice.setKeyChoices(choiceItems); + choice.setMessage("Select your coutry"); + + // current selection value restore + String needSelectValue = null; + flagItem = (String[])flagModel.getSelectedItem(); + if (flagItem != null){ + needSelectValue = flagItem[1]; + } + + // ask for new value + PickChoiceDialog dlg = new PickChoiceDialog(); + dlg.setWindowSize(300, 500); + dlg.showDialog(choice, needSelectValue); + if(choice.isChosen()){ + flagItem = new String[2]; + flagItem[0] = choice.getChoiceValue(); + flagItem[1] = choice.getChoiceKey(); + flagModel.setSelectedItem(flagItem); + } + } + public String getServer() { return this.txtServer.getText(); } @@ -655,18 +786,22 @@ public class ConnectDialog extends MageDialog { private javax.swing.JButton btnFind1; private javax.swing.JButton btnFind2; private javax.swing.JButton btnFind3; + private javax.swing.JButton btnFlagSearch; private javax.swing.JButton btnForgotPassword; private javax.swing.JButton btnRegister; private mage.client.util.gui.countryBox.CountryComboBox cbFlag; private javax.swing.JCheckBox chkAutoConnect; private javax.swing.JCheckBox chkForceUpdateDB; + private javax.swing.Box.Filler filler1; private javax.swing.JButton jProxySettingsButton; + private javax.swing.JLabel lblFastConnect; private javax.swing.JLabel lblFlag; private javax.swing.JLabel lblPassword; private javax.swing.JLabel lblPort; private javax.swing.JLabel lblServer; private javax.swing.JLabel lblStatus; private javax.swing.JLabel lblUserName; + private javax.swing.JPanel panelFlag; private javax.swing.JPasswordField txtPassword; private javax.swing.JTextField txtPort; private javax.swing.JTextField txtServer; diff --git a/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.form b/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.form index 5adaf97cd92..4b4f2fa9812 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.form +++ b/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.form @@ -1,13 +1,6 @@ -
- - - - - - - + @@ -26,19 +19,13 @@ - + - - - - - - - - - - - + + + + + @@ -47,74 +34,173 @@ - - + + - + - - - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.java b/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.java index 07095dbded2..1e5f70cda1e 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.java @@ -1,43 +1,28 @@ /* -* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. -* -* Redistribution and use in source and binary forms, with or without modification, are -* permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, this list of -* conditions and the following disclaimer. -* -* 2. Redistributions in binary form must reproduce the above copyright notice, this list -* of conditions and the following disclaimer in the documentation and/or other materials -* provided with the distribution. -* -* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED -* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR -* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -* -* The views and conclusions contained in the software and documentation are those of the -* authors and should not be interpreted as representing official policies, either expressed -* or implied, of BetaSteward_at_googlemail.com. -*/ - -/* - * PickNumberDialog.java - * - * Created on Feb 25, 2010, 12:03:39 PM + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. */ - package mage.client.dialog; +import java.awt.Dimension; import java.awt.Point; -import java.util.Map; -import java.util.UUID; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.*; +import javax.swing.AbstractAction; +import javax.swing.ActionMap; +import javax.swing.DefaultListModel; +import javax.swing.InputMap; +import javax.swing.JComponent; +import javax.swing.JLabel; import javax.swing.JLayeredPane; +import javax.swing.KeyStroke; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; import mage.choices.Choice; import mage.client.MageFrame; import mage.client.util.SettingsManager; @@ -46,37 +31,140 @@ import mage.client.util.gui.MageDialogState; /** * - * @author BetaSteward_at_googlemail.com + * @author JayDi85 */ + public class PickChoiceDialog extends MageDialog { - /** Creates new form PickNumberDialog */ - public PickChoiceDialog() { - initComponents(); - this.setModal(true); - } Choice choice; - boolean autoSelect; + ArrayList allItems = new ArrayList<>(); + DefaultListModel dataModel = new DefaultListModel(); + + final private static String HTML_TEMPLATE = "
%s
"; + + public void showDialog(Choice choice) { + showDialog(choice, null, null, null); + } + + public void showDialog(Choice choice, String startSelectionValue) { + showDialog(choice, null, null, startSelectionValue); + } public void showDialog(Choice choice, UUID objectId, MageDialogState mageDialogState) { - this.lblMessage.setText("" + choice.getMessage()); + showDialog(choice, objectId, mageDialogState, null); + } + + public void showDialog(Choice choice, UUID objectId, MageDialogState mageDialogState, String startSelectionValue) { this.choice = choice; - this.autoSelect = false; - btnAutoSelect.setVisible(choice.isKeyChoice()); - if (choice.isKeyChoice()){ - - ComboItem[] comboItems = new ComboItem[choice.getKeyChoices().size()]; - int count = 0; - for (Map.Entry entry : choice.getKeyChoices().entrySet()) { - comboItems[count] = new ComboItem(entry.getKey(), entry.getValue()); - count++; + setLabelText(this.labelMessage, choice.getMessage()); + setLabelText(this.labelSubMessage, choice.getSubMessage()); + + btCancel.setEnabled(!choice.isRequired()); + + // 2 modes: string or key-values + // sore data in allItems for inremental filtering + // http://logicbig.com/tutorials/core-java-tutorial/swing/list-filter/ + this.allItems.clear(); + if (choice.isKeyChoice()){ + for (Map.Entry entry: choice.getKeyChoices().entrySet()) { + this.allItems.add(new KeyValueItem(entry.getKey(), entry.getValue())); } - this.lstChoices.setListData(comboItems); } else { - this.lstChoices.setListData(choice.getChoices().toArray()); + for (String value: choice.getChoices()){ + this.allItems.add(new KeyValueItem(value, value)); + } + } + + // sorting + if(choice.isSortEnabled()){ + Collections.sort(this.allItems, new Comparator() { + @Override + public int compare(KeyValueItem o1, KeyValueItem o2) { + Integer n1 = choice.getSortData().get(o1.Key); + Integer n2 = choice.getSortData().get(o2.Key); + return n1.compareTo(n2); + } + }); } + // search + if(choice.isSearchEnabled()) + { + panelSearch.setVisible(true); + this.editSearch.setText(choice.getSearchText()); + }else{ + panelSearch.setVisible(false); + this.editSearch.setText(""); + } + + // listeners for inremental filtering + editSearch.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + choice.setSearchText(editSearch.getText()); + loadData(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + choice.setSearchText(editSearch.getText()); + loadData(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + choice.setSearchText(editSearch.getText()); + loadData(); + } + }); + + // listeners for select up and down without edit focus lost + editSearch.addKeyListener(new KeyListener() { + @Override + public void keyTyped(KeyEvent e) { + //System.out.println("types"); + } + + @Override + public void keyPressed(KeyEvent e) { + if(e.getKeyCode() == KeyEvent.VK_UP){ + doPrevSelect(); + }else if(e.getKeyCode() == KeyEvent.VK_DOWN){ + doNextSelect(); + } + } + + @Override + public void keyReleased(KeyEvent e) { + //System.out.println("released"); + } + }); + + // listeners double click choose + listChoices.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if(e.getClickCount() == 2){ + doChoose(); + } + } + }); + + // listeners for ESC close + if(!choice.isRequired()){ + String cancelName = "cancel"; + InputMap inputMap = getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), cancelName); + ActionMap actionMap = getRootPane().getActionMap(); + actionMap.put(cancelName, new AbstractAction() { + public void actionPerformed(ActionEvent e) { + doCancel(); + } + }); + } + + // window settings MageFrame.getDesktop().add(this, JLayeredPane.PALETTE_LAYER); if (mageDialogState != null) { mageDialogState.setStateToDialog(this); @@ -87,154 +175,316 @@ public class PickChoiceDialog extends MageDialog { GuiDisplayUtil.keepComponentInsideScreen(centered.x, centered.y, this); } + // final load + loadData(); + + // start selection + if((startSelectionValue != null)){ + int selectIndex = -1; + for(int i = 0; i < this.listChoices.getModel().getSize() - 1; i++){ + KeyValueItem listItem = (KeyValueItem)this.listChoices.getModel().getElementAt(i); + if (listItem.Key.equals(startSelectionValue)){ + selectIndex = i; + break; + } + } + + if(selectIndex >= 0){ + this.listChoices.setSelectedIndex(selectIndex); + this.listChoices.ensureIndexIsVisible(selectIndex); + } + } this.setVisible(true); } - - public boolean isAutoSelect() { - return autoSelect; + + public void setWindowSize(int width, int heigth){ + this.setSize(new Dimension(width, heigth)); + } + + private void loadData(){ + // load data to datamodel after filter or on startup + String filter = choice.getSearchText(); + if (filter == null){ filter = ""; } + filter = filter.toLowerCase(); + + this.dataModel.clear(); + for(KeyValueItem item: this.allItems){ + if(!choice.isSearchEnabled() || item.Value.toLowerCase().contains(filter)){ + this.dataModel.addElement(item); + } + } + } + + private void setLabelText(JLabel label, String text){ + if ((text != null) && !text.equals("")){ + label.setText(String.format(HTML_TEMPLATE, text)); + label.setVisible(true); + }else{ + label.setText(""); + label.setVisible(false); + } + } + + private void doNextSelect(){ + int newSel = this.listChoices.getSelectedIndex() + 1; + int maxSel = this.listChoices.getModel().getSize() - 1; + if(newSel <= maxSel){ + this.listChoices.setSelectedIndex(newSel); + this.listChoices.ensureIndexIsVisible(newSel); + } + } + + private void doPrevSelect(){ + int newSel = this.listChoices.getSelectedIndex() - 1; + if(newSel >= 0){ + this.listChoices.setSelectedIndex(newSel); + this.listChoices.ensureIndexIsVisible(newSel); + } } - public void setChoice() { - if (this.lstChoices.getSelectedValue() == null) { - choice.clearChoice(); + private void doChoose(){ + if(setChoice()){ + this.hideDialog(); + } + } + + private void doCancel(){ + this.listChoices.clearSelection(); + this.choice.clearChoice(); + hideDialog(); + } + + /** + * Creates new form PickChoiceDialog + */ + public PickChoiceDialog() { + initComponents(); + this.listChoices.setModel(dataModel); + this.setModal(true); + } + + public boolean setChoice() { + KeyValueItem item = (KeyValueItem)this.listChoices.getSelectedValue(); + + // auto select one item (after incemental filtering) + if((item == null) && (this.listChoices.getModel().getSize() == 1)){ + this.listChoices.setSelectedIndex(0); + item = (KeyValueItem)this.listChoices.getSelectedValue(); } - if (choice.isKeyChoice()) { - ComboItem item = (ComboItem)this.lstChoices.getSelectedValue(); - if (item != null) { - choice.setChoiceByKey(item.getValue()); - } else { - choice.clearChoice(); + if(item != null){ + if(choice.isKeyChoice()){ + choice.setChoiceByKey(item.getKey()); + }else{ + choice.setChoice(item.getKey()); } - } else { - choice.setChoice((String)this.lstChoices.getSelectedValue()); + return true; + }else{ + choice.clearChoice(); + return false; } } + + class KeyValueItem + { + private final String Key; + private final String Value; + + public KeyValueItem(String value, String label) { + this.Key = value; + this.Value = label; + } - /** This method is called from within the constructor to - * initialize the form. - * WARNING: Do NOT modify this code. The content of this method is - * always regenerated by the Form Editor. + public String getKey() { + return this.Key; + } + + public String getValue() { + return this.Value; + } + + @Override + public String toString() { + return this.Value; + } + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. */ @SuppressWarnings("unchecked") // //GEN-BEGIN:initComponents private void initComponents() { - btnAutoSelect = new javax.swing.JButton(); - btnCancel = new javax.swing.JButton(); - btnOk = new javax.swing.JButton(); - jScrollPane1 = new javax.swing.JScrollPane(); - lstChoices = new javax.swing.JList(); - lblMessage = new javax.swing.JLabel(); + panelHeader = new javax.swing.JPanel(); + labelMessage = new javax.swing.JLabel(); + labelSubMessage = new javax.swing.JLabel(); + panelSearch = new javax.swing.JPanel(); + labelSearch = new javax.swing.JLabel(); + editSearch = new javax.swing.JTextField(); + scrollList = new javax.swing.JScrollPane(); + listChoices = new javax.swing.JList(); + panelCommands = new javax.swing.JPanel(); + btOK = new javax.swing.JButton(); + btCancel = new javax.swing.JButton(); - setResizable(true); - setMinimumSize(new java.awt.Dimension(280, 200)); - setName(""); // NOI18N + labelMessage.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + labelMessage.setText("
example long message example long message example long message example long message example long message
"); - btnAutoSelect.setText("Auto select"); - btnAutoSelect.setToolTipText("If you select an effect with \"Auto select\", this effect will be selected the next time automatically first."); - btnAutoSelect.addActionListener(evt -> btnAutoSelectActionPerformed(evt)); + labelSubMessage.setFont(labelSubMessage.getFont().deriveFont((labelSubMessage.getFont().getStyle() | java.awt.Font.ITALIC) | java.awt.Font.BOLD)); + labelSubMessage.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + labelSubMessage.setText("
example long message example long
"); - btnCancel.setText("Cancel"); - btnCancel.addActionListener(evt -> btnCancelActionPerformed(evt)); + javax.swing.GroupLayout panelHeaderLayout = new javax.swing.GroupLayout(panelHeader); + panelHeader.setLayout(panelHeaderLayout); + panelHeaderLayout.setHorizontalGroup( + panelHeaderLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelHeaderLayout.createSequentialGroup() + .addGroup(panelHeaderLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(labelMessage, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 210, Short.MAX_VALUE) + .addComponent(labelSubMessage, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 210, Short.MAX_VALUE)) + .addGap(0, 0, 0)) + ); + panelHeaderLayout.setVerticalGroup( + panelHeaderLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelHeaderLayout.createSequentialGroup() + .addGap(0, 0, 0) + .addComponent(labelMessage) + .addGap(0, 0, 0) + .addComponent(labelSubMessage)) + ); - btnOk.setText("OK"); - btnOk.addActionListener(evt -> btnOkActionPerformed(evt)); + labelSearch.setText("Search:"); - lstChoices.setModel(new javax.swing.AbstractListModel() { - final String[] strings = { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" }; + editSearch.setText("sample search text"); + + javax.swing.GroupLayout panelSearchLayout = new javax.swing.GroupLayout(panelSearch); + panelSearch.setLayout(panelSearchLayout); + panelSearchLayout.setHorizontalGroup( + panelSearchLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelSearchLayout.createSequentialGroup() + .addGap(0, 0, 0) + .addComponent(labelSearch) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(editSearch) + .addGap(0, 0, 0)) + ); + panelSearchLayout.setVerticalGroup( + panelSearchLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelSearchLayout.createSequentialGroup() + .addGap(3, 3, 3) + .addGroup(panelSearchLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(labelSearch) + .addComponent(editSearch, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(3, 3, 3)) + ); + + listChoices.setModel(new javax.swing.AbstractListModel() { + String[] strings = { "item1", "item2", "item3" }; public int getSize() { return strings.length; } public Object getElementAt(int i) { return strings[i]; } }); - jScrollPane1.setViewportView(lstChoices); + scrollList.setViewportView(listChoices); - lblMessage.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); - lblMessage.setText("message"); + btOK.setText("Choose"); + btOK.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btOKActionPerformed(evt); + } + }); + + btCancel.setText("Cancel"); + btCancel.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btCancelActionPerformed(evt); + } + }); + + javax.swing.GroupLayout panelCommandsLayout = new javax.swing.GroupLayout(panelCommands); + panelCommands.setLayout(panelCommandsLayout); + panelCommandsLayout.setHorizontalGroup( + panelCommandsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelCommandsLayout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(btOK) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(btCancel, javax.swing.GroupLayout.PREFERRED_SIZE, 70, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + + panelCommandsLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {btCancel, btOK}); + + panelCommandsLayout.setVerticalGroup( + panelCommandsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(panelCommandsLayout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(panelCommandsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(btCancel) + .addComponent(btOK)) + .addContainerGap()) + ); + + getRootPane().setDefaultButton(btOK); 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(jScrollPane1, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 335, Short.MAX_VALUE) - .addGroup(layout.createSequentialGroup() - .addGap(0, 0, Short.MAX_VALUE) - .addComponent(btnAutoSelect) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(btnOk) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(btnCancel)) - .addComponent(lblMessage, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 335, Short.MAX_VALUE)) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(scrollList, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(panelCommands, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(panelHeader, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(panelSearch, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addContainerGap()) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addGap(6, 6, 6) - .addComponent(lblMessage, javax.swing.GroupLayout.PREFERRED_SIZE, 37, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap() + .addComponent(panelHeader, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 158, Short.MAX_VALUE) + .addComponent(panelSearch, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(btnCancel) - .addComponent(btnOk) - .addComponent(btnAutoSelect)) - .addGap(10, 10, 10)) + .addComponent(scrollList, javax.swing.GroupLayout.DEFAULT_SIZE, 246, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(panelCommands, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) ); pack(); }//
//GEN-END:initComponents - private void btnOkActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnOkActionPerformed - setChoice(); - this.hideDialog(); - }//GEN-LAST:event_btnOkActionPerformed + private void btOKActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btOKActionPerformed + doChoose(); + }//GEN-LAST:event_btOKActionPerformed - private void btnCancelActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnCancelActionPerformed - this.lstChoices.clearSelection(); - this.choice.clearChoice(); - this.hideDialog(); - }//GEN-LAST:event_btnCancelActionPerformed + private void btCancelActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btCancelActionPerformed + doCancel(); + }//GEN-LAST:event_btCancelActionPerformed - private void btnAutoSelectActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnAutoSelectActionPerformed - this.autoSelect = true; - setChoice(); - this.hideDialog(); - }//GEN-LAST:event_btnAutoSelectActionPerformed + /** + * Closes the dialog + */ + private void closeDialog(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_closeDialog + doCancel(); + }//GEN-LAST:event_closeDialog // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JButton btnAutoSelect; - private javax.swing.JButton btnCancel; - private javax.swing.JButton btnOk; - private javax.swing.JScrollPane jScrollPane1; - private javax.swing.JLabel lblMessage; - private javax.swing.JList lstChoices; + private javax.swing.JButton btCancel; + private javax.swing.JButton btOK; + private javax.swing.JTextField editSearch; + private javax.swing.JLabel labelMessage; + private javax.swing.JLabel labelSearch; + private javax.swing.JLabel labelSubMessage; + private javax.swing.JList listChoices; + private javax.swing.JPanel panelCommands; + private javax.swing.JPanel panelHeader; + private javax.swing.JPanel panelSearch; + private javax.swing.JScrollPane scrollList; // End of variables declaration//GEN-END:variables - } - class ComboItem { - - private final String value; - private final String label; - - public ComboItem(String value, String label) { - this.value = value; - this.label = label; - } - - public String getValue() { - return this.value; - } - - public String getLabel() { - return this.label; - } - - @Override - public String toString() { - return label; - } - } \ No newline at end of file diff --git a/Mage.Client/src/main/java/mage/client/game/GamePanel.java b/Mage.Client/src/main/java/mage/client/game/GamePanel.java index a014167df0a..ab0daf3e706 100644 --- a/Mage.Client/src/main/java/mage/client/game/GamePanel.java +++ b/Mage.Client/src/main/java/mage/client/game/GamePanel.java @@ -1220,14 +1220,17 @@ public final class GamePanel extends javax.swing.JPanel { public void getChoice(Choice choice, UUID objectId) { hideAll(); + // TODO: remember last choices and search incremental for same events? PickChoiceDialog pickChoice = new PickChoiceDialog(); pickChoice.showDialog(choice, objectId, choiceWindowState); if (choice.isKeyChoice()) { + SessionHandler.sendPlayerString(gameId, choice.getChoiceKey()); + /* // old code, auto complete was for auto scripting? if (pickChoice.isAutoSelect()) { SessionHandler.sendPlayerString(gameId, '#' + choice.getChoiceKey()); } else { SessionHandler.sendPlayerString(gameId, choice.getChoiceKey()); - } + }*/ } else { SessionHandler.sendPlayerString(gameId, choice.getChoice()); } diff --git a/Mage.Client/src/main/java/mage/client/util/gui/countryBox/CountryItemEditor.java b/Mage.Client/src/main/java/mage/client/util/gui/countryBox/CountryItemEditor.java index 119c7789003..26c088445f8 100644 --- a/Mage.Client/src/main/java/mage/client/util/gui/countryBox/CountryItemEditor.java +++ b/Mage.Client/src/main/java/mage/client/util/gui/countryBox/CountryItemEditor.java @@ -27,44 +27,34 @@ */ package mage.client.util.gui.countryBox; -import java.awt.Color; -import java.awt.Component; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import javax.swing.ImageIcon; -import javax.swing.JLabel; -import javax.swing.JPanel; +import java.awt.*; +import javax.swing.*; +import javax.swing.border.EmptyBorder; import javax.swing.plaf.basic.BasicComboBoxEditor; /** * Editor for JComboBox - * @author wwww.codejava.net + * @author wwww.codejava.net, JayDi85 * */ public class CountryItemEditor extends BasicComboBoxEditor { private final JPanel panel = new JPanel(); private final JLabel labelItem = new JLabel(); - private String selectedValue; - private String selectedImage; + private String[] editValue = new String[2]; public CountryItemEditor() { - panel.setLayout(new GridBagLayout()); - GridBagConstraints constraints = new GridBagConstraints(); - constraints.fill = GridBagConstraints.HORIZONTAL; - constraints.weightx = 1.0; -// constraints.insets = new Insets(2, 5, 2, 2); - constraints.insets = new Insets(0, 5, 0, 0); - + panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); + panel.setBackground(new Color(0, 100,190, 255)); + + panel.add(labelItem); + labelItem.setAlignmentX(Component.LEFT_ALIGNMENT); + labelItem.setMaximumSize(new Dimension(Short.MAX_VALUE, Short.MAX_VALUE)); + labelItem.setBorder(new EmptyBorder(0, 5, 0, 0)); labelItem.setOpaque(false); - labelItem.setHorizontalAlignment(JLabel.LEFT); labelItem.setForeground(Color.WHITE); - - panel.add(labelItem, constraints); -// panel.setBackground(Color.WHITE); - panel.setBackground(new Color(0, 100,190, 255)); - selectedValue = null; + + editValue = null; } @Override @@ -74,21 +64,26 @@ public class CountryItemEditor extends BasicComboBoxEditor { @Override public Object getItem() { - return this.selectedValue; + return this.editValue; } public String getImageItem() { - return this.selectedImage; - } + return this.editValue[1]; + } + @Override public void setItem(Object item) { if (item == null || !(item instanceof String[])) { return; } - String[] countryItem = (String[]) item; - selectedValue = countryItem[0]; - selectedImage = countryItem[1]; - labelItem.setText(selectedValue); - labelItem.setIcon(new ImageIcon(getClass().getResource("/flags/"+ countryItem[1] + ".png"))); + + String[] newItem = (String[])item; + + editValue = new String[2]; + editValue[0] = newItem[0]; + editValue[1] = newItem[1]; + + labelItem.setText(editValue[0]); + labelItem.setIcon(new ImageIcon(getClass().getResource("/flags/"+ editValue[1] + ".png"))); } } diff --git a/Mage.Client/src/main/java/mage/client/util/gui/countryBox/MyComboBoxRenderer.java b/Mage.Client/src/main/java/mage/client/util/gui/countryBox/MyComboBoxRenderer.java index ec302456b16..d174e5e1eb5 100644 --- a/Mage.Client/src/main/java/mage/client/util/gui/countryBox/MyComboBoxRenderer.java +++ b/Mage.Client/src/main/java/mage/client/util/gui/countryBox/MyComboBoxRenderer.java @@ -50,8 +50,10 @@ public class MyComboBoxRenderer extends JLabel implements ListCellRenderer { @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { - setText(value.toString()); + + String[] val = (String[]) value; + setText(val[0]); + return this; } - } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSets.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSets.java index 1d01f9f7707..38b5ff2b434 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSets.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSets.java @@ -6,22 +6,20 @@ import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.Iterator; - import mage.cards.ExpansionSet; import mage.cards.Sets; import mage.client.constants.Constants; import mage.constants.Rarity; +import org.apache.log4j.Logger; import org.mage.plugins.card.dl.DownloadJob; - import static org.mage.plugins.card.dl.DownloadJob.fromURL; import static org.mage.plugins.card.dl.DownloadJob.toFile; import static org.mage.plugins.card.utils.CardImageUtils.getImagesDir; -import org.apache.log4j.Logger; - public class GathererSets implements Iterable { private class CheckResult { + String code; ExpansionSet set; boolean haveCommon; @@ -69,12 +67,12 @@ public class GathererSets implements Iterable { //"ARENA" -- is't many set with different codes, not one "CLASH", "CP", "DD3GVL", "DPA", "EURO", "FNMP", "GPX", "GRC", "GUR", "H17", "JR", "MBP", "MGDC", "MLP", "MPRP", "MPS-AKH", "PTC", "S00", "S99", "SUS", "SWS", "UGIN", "UGL", "V10", "V17", "WMCQ", // need to fix "H09", "PD2", "PD3", "UNH", "CM1", "E02", "V11", "M25", "UST", "IMA", "DD2", "EVG", "DDC", "DDE", "DDD", "DDT", "8EB", "9EB", "CHR" // ok - // current testing + // current testing }; private static final String[] symbolsBasicWithMyth = {"M10", "M11", "M12", "M13", "M14", "M15", "ORI", "DDF", "DDG", "DDH", "DDI", "DDJ", "DDK", "DDL", "DDM", "DDN", - "DD3DVD", "DD3JVC", "DDO", "DDP", "DDQ", "DDR", "DDS", + "DD3DVD", "DD3JVC", "DDO", "DDP", "DDQ", "DDR", "DDS", "DDT", "ALA", "CON", "ARB", "ZEN", "WWK", "ROE", "SOM", "MBS", "NPH", @@ -155,7 +153,7 @@ public class GathererSets implements Iterable { outDir = new File(getImagesDir() + Constants.RESOURCE_PATH_SYMBOLS); - if (!outDir.exists()){ + if (!outDir.exists()) { outDir.mkdirs(); } } @@ -163,6 +161,7 @@ public class GathererSets implements Iterable { // checks for wrong card settings and support (easy to control what all good) private static final HashMap setsToDownload = new HashMap<>(); private static final HashMap codesToIgnoreCheck = new HashMap<>(); + static { // xMage have inner sets for 8th and 9th Edition for booster workaround (cards from core game do not include in boosters) // see https://mtg.gamepedia.com/8th_Edition/Core_Game @@ -172,7 +171,7 @@ public class GathererSets implements Iterable { } private void CheckSearchResult(String searchCode, ExpansionSet foundedExp, boolean canDownloadTask, - boolean haveCommon, boolean haveUncommon, boolean haveRare, boolean haveMyth){ + boolean haveCommon, boolean haveUncommon, boolean haveRare, boolean haveMyth) { // duplicated in settings CheckResult res = setsToDownload.get(searchCode); @@ -190,9 +189,8 @@ public class GathererSets implements Iterable { } // checks for founded sets only - // to early to download - if (!canDownloadTask){ + if (!canDownloadTask) { Calendar c = Calendar.getInstance(); c.setTime(foundedExp.getReleaseDate()); c.add(Calendar.DATE, -1 * DAYS_BEFORE_RELEASE_TO_DOWNLOAD); @@ -201,14 +199,14 @@ public class GathererSets implements Iterable { } } - private void AnalyseSearchResult(){ + private void AnalyseSearchResult() { // analyze supported sets and show wrong settings Date startedDate = new Date(); for (ExpansionSet set : Sets.getInstance().values()) { // ignore some inner sets - if (codesToIgnoreCheck.get(set.getCode()) != null){ + if (codesToIgnoreCheck.get(set.getCode()) != null) { continue; } @@ -239,21 +237,20 @@ public class GathererSets implements Iterable { //*/ // 3. info: sets with alternative numbers - for(ExpansionSet.SetCardInfo card: set.getSetCardInfo()){ - if (String.valueOf(card.getCardNumberAsInt()).length() != card.getCardNumber().length()){ + for (ExpansionSet.SetCardInfo card : set.getSetCardInfo()) { + if (String.valueOf(card.getCardNumberAsInt()).length() != card.getCardNumber().length()) { logger.info(String.format("Symbols: set have alternative card but do not config to it: %s (%s)", set.getCode(), set.getName())); break; } } // 4. info: sets with missing cards for boosters (todo: what about +20 number for alternative land arts?) - if (set.getMaxCardNumberInBooster() != Integer.MAX_VALUE) - { - for(ExpansionSet.SetCardInfo card: set.getSetCardInfo()){ - if (card.getCardNumberAsInt() > set.getMaxCardNumberInBooster()){ + if (set.getMaxCardNumberInBooster() != Integer.MAX_VALUE) { + for (ExpansionSet.SetCardInfo card : set.getSetCardInfo()) { + if (card.getCardNumberAsInt() > set.getMaxCardNumberInBooster()) { if (card.getRarity() == Rarity.LAND) { logger.info(String.format("Symbols: set's booster have land above max card number: %s (%s), %s - %s", set.getCode(), set.getName(), card.getCardNumber(), card.getName())); - }else { + } else { logger.info(String.format("Symbols: set's booster missing nonland card:: %s (%s), %s - %s", set.getCode(), set.getName(), card.getCardNumber(), card.getName())); } } @@ -337,4 +334,4 @@ public class GathererSets implements Iterable { String url = "http://gatherer.wizards.com/Handlers/Image.ashx?type=symbol&set=" + set + "&size=small&rarity=" + urlRarity; return new DownloadJob(set + '-' + rarity, fromURL(url), toFile(dst)); } -} \ No newline at end of file +} diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MagicCardsImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MagicCardsImageSource.java index 0a8b1ba95cb..0cfefa736a4 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MagicCardsImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MagicCardsImageSource.java @@ -262,6 +262,7 @@ public enum MagicCardsImageSource implements CardImageSource { put("DDQ", "duel-decks-blessed-vs-cursed"); put("DDR", "duel-decks-nissa-vs-ob-nixilis"); put("DDS", "duel-decks-mind-vs-might"); + put("DDS", "duel-decks-merfolk-vs-goblin"); put("DGM", "dragons-maze"); put("DKA", "dark-ascension"); put("DRB", "from-the-vault-dragons"); diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/WizardCardsImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/WizardCardsImageSource.java index 301edb4757d..efc266a49ec 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/WizardCardsImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/WizardCardsImageSource.java @@ -45,6 +45,9 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.prefs.Preferences; +import mage.cards.repository.CardCriteria; +import mage.cards.repository.CardInfo; +import mage.cards.repository.CardRepository; import mage.client.MageFrame; import mage.client.dialog.PreferencesDialog; import mage.remote.Connection; @@ -332,6 +335,7 @@ public enum WizardCardsImageSource implements CardImageSource { setsAliases.put("DDQ", "Duel Decks: Blessed vs. Cursed"); setsAliases.put("DDR", "Duel Decks: Nissa vs. Ob Nixilis"); setsAliases.put("DDS", "Duel Decks: Mind vs. Might"); + setsAliases.put("DDT", "Duel Decks: Merfolk vs. Goblins"); setsAliases.put("DGM", "Dragon's Maze"); setsAliases.put("DIS", "Dissension"); setsAliases.put("DKA", "Dark Ascension"); @@ -473,6 +477,56 @@ public enum WizardCardsImageSource implements CardImageSource { return null; } + @Override + public String generateURL(CardDownloadData card) throws Exception { + String collectorId = card.getCollectorId(); + String cardSet = card.getSet(); + if (collectorId == null || cardSet == null) { + throw new Exception("Wrong parameters for image: collector id: " + collectorId + ",card set: " + cardSet); + } + if (card.isFlippedSide()) { //doesn't support rotated images + return null; + } + String setNames = setsAliases.get(cardSet); + if (setNames != null) { + Map setLinks = sets.computeIfAbsent(cardSet, k -> getSetLinks(cardSet)); + if (setLinks == null || setLinks.isEmpty()) { + return null; + } + String link = setLinks.get(card.getDownloadName().toLowerCase()); + if (link == null) { + int length = collectorId.length(); + + if (Character.isLetter(collectorId.charAt(length - 1))) { + length -= 1; + } + int number = Integer.parseInt(collectorId.substring(0, length)); + if (number > 0) { + String key = card.getDownloadName().toLowerCase() + number; + link = setLinks.get(key); + } + if (link == null) { + List l = new ArrayList<>(setLinks.values()); + if (l.size() >= number) { + link = l.get(number - 1); + } else {; + link = l.get(number - 21); + if (link != null) { + link = link.replace(Integer.toString(number - 20), (Integer.toString(number - 20) + 'a')); + } + } + } + } + if (link != null && !link.startsWith("http://")) { + link = "http://gatherer.wizards.com" + link; + } + return link; + } + + return null; + + } + private Map getSetLinks(String cardSet) { LinkedHashMap setLinks = new LinkedHashMap<>(); ExecutorService executor = Executors.newFixedThreadPool(10); @@ -503,8 +557,12 @@ public enum WizardCardsImageSource implements CardImageSource { } String cardName = normalizeName(cardsImages.get(i).attr("alt")); if (cardName != null && !cardName.isEmpty()) { - Runnable task = new GetImageLinkTask(multiverseId, cardName, preferedLanguage, setLinks); - executor.execute(task); + if (cardName.equals("Forest") || cardName.equals("Swamp") || cardName.equals("Mountain") || cardName.equals("Island") || cardName.equals("Plains")) { + getLandVariations(setLinks, cardSet, multiverseId, cardName); + } else { + Integer preferedMultiverseId = getLocalizedMultiverseId(preferedLanguage, multiverseId); + setLinks.put(cardName.toLowerCase(), generateLink(preferedMultiverseId)); + } } } page++; @@ -553,23 +611,38 @@ public enum WizardCardsImageSource implements CardImageSource { return doc; } - private Map getLandVariations(int multiverseId, String cardName) throws IOException, NumberFormatException { + private void getLandVariations(LinkedHashMap setLinks, String cardSet, int multiverseId, String cardName) throws IOException, NumberFormatException { + CardCriteria criteria = new CardCriteria(); + criteria.name(cardName); + criteria.setCodes(cardSet); + List cards = CardRepository.instance.findCards(criteria); + String urlLandDocument = "http://gatherer.wizards.com/Pages/Card/Details.aspx?multiverseid=" + multiverseId; Document landDoc = getDocument(urlLandDocument); Elements variations = landDoc.select("a.variationlink"); - Map links = new HashMap<>(); if (!variations.isEmpty()) { - int landNumber = 1; + if (variations.size() > cards.size()) { + logger.warn("More links for lands than cards in DB found for set: " + cardSet + " Name: " + cardName); + } + if (variations.size() < cards.size()) { + logger.warn("Less links for lands than cards in DB found for set: " + cardSet + " Name: " + cardName); + } + int iteration = 0; for (Element variation : variations) { + String colNumb = String.valueOf(iteration); + if (cards.size() > iteration) { + CardInfo cardInfo = cards.get(iteration); + if (cardInfo != null) { + colNumb = cardInfo.getCardNumber(); + } + } Integer landMultiverseId = Integer.parseInt(variation.attr("href").replaceAll("[^\\d]", "")); - links.put((cardName + landNumber).toLowerCase(), generateLink(landMultiverseId)); - landNumber++; + setLinks.put((cardName).toLowerCase() + colNumb, generateLink(landMultiverseId)); + iteration++; } } else { - links.put(cardName.toLowerCase(), generateLink(multiverseId)); + setLinks.put(cardName.toLowerCase(), generateLink(multiverseId)); } - - return links; } private static String generateLink(int landMultiverseId) { @@ -627,50 +700,6 @@ public enum WizardCardsImageSource implements CardImageSource { .replace("Hintreland Scourge", "Hinterland Scourge"); } - @Override - public String generateURL(CardDownloadData card) throws Exception { - String collectorId = card.getCollectorId(); - String cardSet = card.getSet(); - if (collectorId == null || cardSet == null) { - throw new Exception("Wrong parameters for image: collector id: " + collectorId + ",card set: " + cardSet); - } - if (card.isFlippedSide()) { //doesn't support rotated images - return null; - } - String setNames = setsAliases.get(cardSet); - if (setNames != null) { - Map setLinks = sets.computeIfAbsent(cardSet, k -> getSetLinks(cardSet)); - if (setLinks == null || setLinks.isEmpty()) { - return null; - } - String link = setLinks.get(card.getDownloadName().toLowerCase()); - if (link == null) { - int length = collectorId.length(); - - if (Character.isLetter(collectorId.charAt(length - 1))) { - length -= 1; - } - - int number = Integer.parseInt(collectorId.substring(0, length)); - List l = new ArrayList<>(setLinks.values()); - if (l.size() >= number) { - link = l.get(number - 1); - } else {; - link = l.get(number - 21); - if (link != null) { - link = link.replace(Integer.toString(number - 20), (Integer.toString(number - 20) + 'a')); - } - } - } - if (link != null && !link.startsWith("http://")) { - link = "http://gatherer.wizards.com" + link; - } - return link; - } - return null; - - } - @Override public String generateTokenUrl(CardDownloadData card) { return null; @@ -681,44 +710,43 @@ public enum WizardCardsImageSource implements CardImageSource { return 60.0f; } - private final class GetImageLinkTask implements Runnable { - - private int multiverseId; - private String cardName; - private String preferedLanguage; - private LinkedHashMap setLinks; - - public GetImageLinkTask(int multiverseId, String cardName, String preferedLanguage, LinkedHashMap setLinks) { - try { - this.multiverseId = multiverseId; - this.cardName = cardName; - this.preferedLanguage = preferedLanguage; - this.setLinks = setLinks; - } catch (Exception ex) { - logger.error(ex.getMessage()); - logger.error("multiverseId: " + multiverseId); - logger.error("cardName: " + cardName); - logger.error("preferedLanguage: " + preferedLanguage); - logger.error("setLinks: " + setLinks.toString()); - } - } - - @Override - public void run() { - try { - if (cardName.equals("Forest") || cardName.equals("Swamp") || cardName.equals("Mountain") || cardName.equals("Island") || cardName.equals("Plains")) { - setLinks.putAll(getLandVariations(multiverseId, cardName)); - } else { - Integer preferedMultiverseId = getLocalizedMultiverseId(preferedLanguage, multiverseId); - setLinks.put(cardName.toLowerCase(), generateLink(preferedMultiverseId)); - } - } catch (IOException | NumberFormatException ex) { - logger.error("Exception when parsing the wizards page: " + ex.getMessage()); - } - } - - } - +// private final class GetImageLinkTask implements Runnable { +// +// private int multiverseId; +// private String cardName; +// private String preferedLanguage; +// private LinkedHashMap setLinks; +// +// public GetImageLinkTask(int multiverseId, String cardName, String preferedLanguage, LinkedHashMap setLinks) { +// try { +// this.multiverseId = multiverseId; +// this.cardName = cardName; +// this.preferedLanguage = preferedLanguage; +// this.setLinks = setLinks; +// } catch (Exception ex) { +// logger.error(ex.getMessage()); +// logger.error("multiverseId: " + multiverseId); +// logger.error("cardName: " + cardName); +// logger.error("preferedLanguage: " + preferedLanguage); +// logger.error("setLinks: " + setLinks.toString()); +// } +// } +// +// @Override +// public void run() { +// try { +// if (cardName.equals("Forest") || cardName.equals("Swamp") || cardName.equals("Mountain") || cardName.equals("Island") || cardName.equals("Plains")) { +// setLinks.putAll(getLandVariations(multiverseId, cardName)); +// } else { +// Integer preferedMultiverseId = getLocalizedMultiverseId(preferedLanguage, multiverseId); +// setLinks.put(cardName.toLowerCase(), generateLink(preferedMultiverseId)); +// } +// } catch (IOException | NumberFormatException ex) { +// logger.error("Exception when parsing the wizards page: " + ex.getMessage()); +// } +// } +// +// } @Override public int getTotalImages() { return -1; diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/images/ImageCache.java b/Mage.Client/src/main/java/org/mage/plugins/card/images/ImageCache.java index 337b3daeb5a..354c197ecd1 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/images/ImageCache.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/images/ImageCache.java @@ -327,7 +327,7 @@ public final class ImageCache { BufferedImage cornerImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB); // corner - float ROUNDED_CORNER_SIZE = 0.15f; // see CardPanelComponentImpl + float ROUNDED_CORNER_SIZE = 0.11f; // see CardPanelComponentImpl int cornerSizeBorder = Math.max(4, Math.round(image.getWidth() * ROUNDED_CORNER_SIZE)); // corner mask diff --git a/Mage.Client/src/main/resources/buttons/search_128.png b/Mage.Client/src/main/resources/buttons/search_128.png new file mode 100644 index 00000000000..574748a87a0 Binary files /dev/null and b/Mage.Client/src/main/resources/buttons/search_128.png differ diff --git a/Mage.Client/src/main/resources/buttons/search_24.png b/Mage.Client/src/main/resources/buttons/search_24.png new file mode 100644 index 00000000000..a3cd50658eb Binary files /dev/null and b/Mage.Client/src/main/resources/buttons/search_24.png differ diff --git a/Mage.Client/src/main/resources/buttons/search_32.png b/Mage.Client/src/main/resources/buttons/search_32.png new file mode 100644 index 00000000000..d78217ac92f Binary files /dev/null and b/Mage.Client/src/main/resources/buttons/search_32.png differ diff --git a/Mage.Client/src/main/resources/buttons/search_64.png b/Mage.Client/src/main/resources/buttons/search_64.png new file mode 100644 index 00000000000..d2b766cfe12 Binary files /dev/null and b/Mage.Client/src/main/resources/buttons/search_64.png differ diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/IxalanBlock.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/IxalanBlock.java new file mode 100644 index 00000000000..48e7b89f923 --- /dev/null +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/IxalanBlock.java @@ -0,0 +1,43 @@ +/* +* Copyright 2011 BetaSteward_at_googlemail.com. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, are +* permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, this list of +* conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright notice, this list +* of conditions and the following disclaimer in the documentation and/or other materials +* provided with the distribution. +* +* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED +* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR +* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +* The views and conclusions contained in the software and documentation are those of the +* authors and should not be interpreted as representing official policies, either expressed +* or implied, of BetaSteward_at_googlemail.com. + */ +package mage.deck; + +import mage.cards.decks.Constructed; + +/** + * + * @author LevelX2 + */ +public class IxalanBlock extends Constructed { + + public IxalanBlock() { + super("Constructed - Ixalan Block"); + setCodes.add("XLN"); + setCodes.add("RIX"); + } +} diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/LorwynBlock.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/LorwynBlock.java new file mode 100644 index 00000000000..376965d3666 --- /dev/null +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/LorwynBlock.java @@ -0,0 +1,43 @@ +/* +* Copyright 2011 BetaSteward_at_googlemail.com. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, are +* permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, this list of +* conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright notice, this list +* of conditions and the following disclaimer in the documentation and/or other materials +* provided with the distribution. +* +* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED +* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR +* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +* The views and conclusions contained in the software and documentation are those of the +* authors and should not be interpreted as representing official policies, either expressed +* or implied, of BetaSteward_at_googlemail.com. + */ +package mage.deck; + +import mage.cards.decks.Constructed; + +/** + * + * @author LevelX2 + */ +public class LorwynBlock extends Constructed { + + public LorwynBlock() { + super("Constructed - Lorwyn Block"); + setCodes.add("LRW"); + setCodes.add("MOR"); + } +} diff --git a/Mage.Server/config/config.xml b/Mage.Server/config/config.xml index bfb4e4436c1..553261ff8eb 100644 --- a/Mage.Server/config/config.xml +++ b/Mage.Server/config/config.xml @@ -78,7 +78,7 @@ - + @@ -156,9 +156,11 @@ + + diff --git a/Mage.Server/release/config/config.xml b/Mage.Server/release/config/config.xml index b416ca297a9..9413a83ba1a 100644 --- a/Mage.Server/release/config/config.xml +++ b/Mage.Server/release/config/config.xml @@ -149,18 +149,20 @@ - - + + + + - + diff --git a/Mage.Sets/src/mage/cards/a/AphettoDredging.java b/Mage.Sets/src/mage/cards/a/AphettoDredging.java index 3e455021cf0..74df66d9838 100644 --- a/Mage.Sets/src/mage/cards/a/AphettoDredging.java +++ b/Mage.Sets/src/mage/cards/a/AphettoDredging.java @@ -65,7 +65,7 @@ public class AphettoDredging extends CardImpl { if (ability instanceof SpellAbility) { Player controller = game.getPlayer(ability.getControllerId()); if (controller != null) { - Choice typeChoice = new ChoiceCreatureType(); + Choice typeChoice = new ChoiceCreatureType(game.getObject(ability.getSourceId())); while (!controller.choose(Outcome.PutCreatureInPlay, typeChoice, game)) { if (!controller.canRespond()) { return; diff --git a/Mage.Sets/src/mage/cards/b/BloodlineShaman.java b/Mage.Sets/src/mage/cards/b/BloodlineShaman.java index c8e5591919d..4e9867eaa16 100644 --- a/Mage.Sets/src/mage/cards/b/BloodlineShaman.java +++ b/Mage.Sets/src/mage/cards/b/BloodlineShaman.java @@ -98,7 +98,7 @@ class BloodlineShamanEffect extends OneShotEffect { MageObject sourceObject = game.getObject(source.getSourceId()); if (controller != null) { // Choose a creature type. - Choice typeChoice = new ChoiceCreatureType(); + Choice typeChoice = new ChoiceCreatureType(sourceObject); while (!controller.choose(outcome, typeChoice, game)) { if (!controller.canRespond()) { return false; diff --git a/Mage.Sets/src/mage/cards/c/CallerOfTheHunt.java b/Mage.Sets/src/mage/cards/c/CallerOfTheHunt.java index 1249f6b1266..4e440094aa4 100644 --- a/Mage.Sets/src/mage/cards/c/CallerOfTheHunt.java +++ b/Mage.Sets/src/mage/cards/c/CallerOfTheHunt.java @@ -129,7 +129,7 @@ class ChooseCreatureTypeEffect extends OneShotEffect { // code by LevelX2, but t Player controller = game.getPlayer(source.getControllerId()); MageObject mageObject = game.getObject(source.getSourceId()); if (controller != null && mageObject != null) { - Choice typeChoice = new ChoiceCreatureType(); + Choice typeChoice = new ChoiceCreatureType(mageObject); while (!controller.choose(outcome, typeChoice, game)) { if (!controller.canRespond()) { return false; diff --git a/Mage.Sets/src/mage/cards/c/CallousOppressor.java b/Mage.Sets/src/mage/cards/c/CallousOppressor.java index 63acbd3c2e1..f5b9d78b018 100644 --- a/Mage.Sets/src/mage/cards/c/CallousOppressor.java +++ b/Mage.Sets/src/mage/cards/c/CallousOppressor.java @@ -44,7 +44,7 @@ import mage.abilities.effects.common.continuous.GainControlTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.choices.Choice; -import mage.choices.ChoiceImpl; +import mage.choices.ChoiceCreatureType; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Duration; @@ -154,9 +154,8 @@ class CallousOppressorChooseCreatureTypeEffect extends OneShotEffect { } Player opponent = game.getPlayer(target.getFirstTarget()); if (opponent != null && mageObject != null) { - Choice typeChoice = new ChoiceImpl(true); + Choice typeChoice = new ChoiceCreatureType(mageObject); typeChoice.setMessage("Choose creature type"); - typeChoice.setChoices(SubType.getCreatureTypes(false).stream().map(SubType::toString).collect(Collectors.toCollection(LinkedHashSet::new))); while (!opponent.choose(outcome, typeChoice, game)) { if (!opponent.canRespond()) { return false; diff --git a/Mage.Sets/src/mage/cards/c/ChargingCinderhorn.java b/Mage.Sets/src/mage/cards/c/ChargingCinderhorn.java index b6b782dc709..5ec6e62c78c 100644 --- a/Mage.Sets/src/mage/cards/c/ChargingCinderhorn.java +++ b/Mage.Sets/src/mage/cards/c/ChargingCinderhorn.java @@ -68,7 +68,7 @@ public class ChargingCinderhorn extends CardImpl { // At the beginning of each player's end step, if no creatures attacked this turn, put a fury counter on Charging Cinderhorn. Then Charging Cinderhorn deals damage equal to the number of fury counters on it to that player. ChargingCinderhornDamageTargetEffect effect = new ChargingCinderhornDamageTargetEffect(); - effect.setText("if no creatures attacked this turn, put a fury counter on {this}. Then {this} deals damage equal to the number of fury counters on it to that player"); + effect.setText("put a fury counter on {this}. Then {this} deals damage equal to the number of fury counters on it to that player"); BeginningOfEndStepTriggeredAbility ability = new BeginningOfEndStepTriggeredAbility(Zone.BATTLEFIELD, effect, TargetController.ANY, new ChargingCinderhornCondition(), false); this.addAbility(ability, new AttackedThisTurnWatcher()); @@ -97,7 +97,7 @@ class ChargingCinderhornCondition implements Condition { @Override public String toString() { - return "no creatures attacked this turn"; + return "if no creatures attacked this turn"; } } diff --git a/Mage.Sets/src/mage/cards/c/CoordinatedBarrage.java b/Mage.Sets/src/mage/cards/c/CoordinatedBarrage.java index d9c068c192c..518dfc76ace 100644 --- a/Mage.Sets/src/mage/cards/c/CoordinatedBarrage.java +++ b/Mage.Sets/src/mage/cards/c/CoordinatedBarrage.java @@ -89,7 +89,7 @@ class CoordinatedBarrageEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - Choice choice = new ChoiceCreatureType(); + Choice choice = new ChoiceCreatureType(game.getObject(source.getSourceId())); if (controller.choose(Outcome.Damage, choice, game)) { String chosenType = choice.getChoice(); FilterControlledPermanent filter = new FilterControlledPermanent(); diff --git a/Mage.Sets/src/mage/cards/c/CragSaurian.java b/Mage.Sets/src/mage/cards/c/CragSaurian.java new file mode 100644 index 00000000000..4f5ba77bde2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CragSaurian.java @@ -0,0 +1,146 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.c; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.GainControlTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author wetterlicht & L_J + */ +public class CragSaurian extends CardImpl { + + public CragSaurian(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{R}{R}{R}"); + this.subtype.add(SubType.LIZARD); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Whenever a source deals damage to Crag Saurian, that source's controller gains control of Crag Saurian. + this.addAbility(new CragSaurianTriggeredAbility()); + } + + public CragSaurian(final CragSaurian card) { + super(card); + } + + @Override + public CragSaurian copy() { + return new CragSaurian(this); + } + + private static class CragSaurianEffect extends OneShotEffect { + + public CragSaurianEffect() { + super(Outcome.GainControl); + this.staticText = "that source's controller gains control of {this}"; + } + + private CragSaurianEffect(CragSaurianEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Player newController = game.getPlayer(this.getTargetPointer().getFirst(game, source)); + if (newController != null && controller != null && !controller.equals(newController)) { + ContinuousEffect effect = new GainControlTargetEffect(Duration.Custom, newController.getId()); + effect.setTargetPointer(new FixedTarget(source.getSourceId())); + game.addEffect(effect, source); + return true; + } + return false; + } + + @Override + public Effect copy() { + return new CragSaurianEffect(this); + } + } + + class CragSaurianTriggeredAbility extends TriggeredAbilityImpl { + + CragSaurianTriggeredAbility() { + super(Zone.BATTLEFIELD, new CragSaurianEffect()); + } + + CragSaurianTriggeredAbility(final CragSaurianTriggeredAbility ability) { + super(ability); + } + + @Override + public CragSaurianTriggeredAbility copy() { + return new CragSaurianTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == EventType.DAMAGED_CREATURE; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getTargetId().equals(this.sourceId)) { + UUID controller = game.getControllerId(event.getSourceId()); + if (controller != null) { + Player player = game.getPlayer(controller); + if (player != null) { + getEffects().get(0).setTargetPointer(new FixedTarget(player.getId())); + return true; + } + } + } + return false; + } + + @Override + public String getRule() { + return "Whenever a source deals damage to {this}, " + super.getRule(); + } + } +} diff --git a/Mage.Sets/src/mage/cards/c/Curfew.java b/Mage.Sets/src/mage/cards/c/Curfew.java index bfe79287b46..632733fd2fa 100644 --- a/Mage.Sets/src/mage/cards/c/Curfew.java +++ b/Mage.Sets/src/mage/cards/c/Curfew.java @@ -27,7 +27,6 @@ */ package mage.cards.c; -import java.util.List; import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.Effect; @@ -37,7 +36,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; @@ -50,7 +49,7 @@ import mage.target.common.TargetControlledCreaturePermanent; public class Curfew extends CardImpl { public Curfew(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{U}"); + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}"); // Each player returns a creature he or she controls to its owner's hand. this.getSpellAbility().addEffect(new CurfewEffect()); @@ -66,7 +65,7 @@ public class Curfew extends CardImpl { } } -class CurfewEffect extends OneShotEffect{ +class CurfewEffect extends OneShotEffect { public CurfewEffect() { super(Outcome.ReturnToHand); @@ -78,26 +77,20 @@ class CurfewEffect extends OneShotEffect{ game.informPlayers("Each player returns a creature he or she controls to its owner's hand"); for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { Player player = game.getPlayer(playerId); - if (player != null) { - TargetControlledCreaturePermanent target = new TargetControlledCreaturePermanent(); - List liste = game.getBattlefield().getActivePermanents(new FilterControlledCreaturePermanent(), playerId, game); - if(!liste.isEmpty()){ - player.choose(Outcome.ReturnToHand, target, source.getSourceId(), game); - - Permanent permanent = game.getPermanent(target.getFirstTarget()); - if (permanent != null) { - permanent.moveToZone(Zone.HAND, source.getSourceId(), game, false); - } + if (player != null && game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_CREATURE, playerId, game) > 0) { + TargetControlledCreaturePermanent target = new TargetControlledCreaturePermanent(1, 1, StaticFilters.FILTER_CONTROLLED_CREATURE, true); + player.choose(Outcome.ReturnToHand, target, source.getSourceId(), game); + Permanent permanent = game.getPermanent(target.getFirstTarget()); + if (permanent != null) { + player.moveCards(permanent, Zone.HAND, source, game); } } } return true; } - @Override public Effect copy() { return new CurfewEffect(); } } - diff --git a/Mage.Sets/src/mage/cards/d/DistantMelody.java b/Mage.Sets/src/mage/cards/d/DistantMelody.java index e773eeeedf9..5502cf22328 100644 --- a/Mage.Sets/src/mage/cards/d/DistantMelody.java +++ b/Mage.Sets/src/mage/cards/d/DistantMelody.java @@ -89,7 +89,7 @@ class DistantMelodyEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); if (player != null) { - Choice typeChoice = new ChoiceCreatureType(); + Choice typeChoice = new ChoiceCreatureType(game.getObject(source.getSourceId())); while (!player.choose(Outcome.BoostCreature, typeChoice, game)) { if (!player.canRespond()) { return false; diff --git a/Mage.Sets/src/mage/cards/e/ElvishSoultiller.java b/Mage.Sets/src/mage/cards/e/ElvishSoultiller.java index 8b20c146bac..51047e50535 100644 --- a/Mage.Sets/src/mage/cards/e/ElvishSoultiller.java +++ b/Mage.Sets/src/mage/cards/e/ElvishSoultiller.java @@ -96,7 +96,7 @@ class ElvishSoultillerEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); MageObject mageObject = game.getObject(source.getSourceId()); if (controller != null && mageObject != null) { - Choice typeChoice = new ChoiceCreatureType(); + Choice typeChoice = new ChoiceCreatureType(mageObject); while (!controller.choose(outcome, typeChoice, game)) { if (!controller.canRespond()) { return false; diff --git a/Mage.Sets/src/mage/cards/e/Extinction.java b/Mage.Sets/src/mage/cards/e/Extinction.java index 5d263271695..0310e869336 100644 --- a/Mage.Sets/src/mage/cards/e/Extinction.java +++ b/Mage.Sets/src/mage/cards/e/Extinction.java @@ -83,7 +83,7 @@ class ExtinctionEffect extends OneShotEffect { Player player = game.getPlayer(source.getControllerId()); MageObject sourceObject = game.getObject(source.getSourceId()); if (player != null) { - Choice typeChoice = new ChoiceCreatureType(); + Choice typeChoice = new ChoiceCreatureType(sourceObject); while (!player.choose(outcome, typeChoice, game)) { if (!player.canRespond()) { return false; diff --git a/Mage.Sets/src/mage/cards/f/FacesOfThePast.java b/Mage.Sets/src/mage/cards/f/FacesOfThePast.java new file mode 100644 index 00000000000..bb77709a656 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FacesOfThePast.java @@ -0,0 +1,107 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.f; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * + * @author LevelX2 & L_J + */ +public class FacesOfThePast extends CardImpl { + + public FacesOfThePast(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{2}{U}"); + + // Whenever a creature dies, tap all untapped creatures that share a creature type with it or untap all tapped creatures that share a creature type with it. + this.addAbility(new DiesCreatureTriggeredAbility(new FacesOfThePastEffect(), false, false, true)); + } + + public FacesOfThePast(final FacesOfThePast card) { + super(card); + } + + @Override + public FacesOfThePast copy() { + return new FacesOfThePast(this); + } +} + +class FacesOfThePastEffect extends OneShotEffect { + + public FacesOfThePastEffect() { + super(Outcome.Benefit); + this.staticText = "tap all untapped creatures that share a creature type with it or untap all tapped creatures that share a creature type with it"; + } + + public FacesOfThePastEffect(final FacesOfThePastEffect effect) { + super(effect); + } + + @Override + public FacesOfThePastEffect copy() { + return new FacesOfThePastEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent targetPermanent = (Permanent) game.getLastKnownInformation(this.getTargetPointer().getFirst(game, source), Zone.BATTLEFIELD); + if (targetPermanent != null) { + Player controller = game.getPlayer(targetPermanent.getControllerId()); + if (controller != null) { + if (controller.chooseUse(outcome, "Tap all untapped creatures that share a creature type with " + targetPermanent.getLogName() + "? (Otherwise, untaps all tapped)", source, game)) { + for (Permanent permanent : game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, game)) { + if (!permanent.isTapped() && targetPermanent.shareSubtypes(permanent, game)) { + permanent.tap(game); + } + } + } else { + for (Permanent permanent : game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, game)) { + if (permanent.isTapped() && targetPermanent.shareSubtypes(permanent, game)) { + permanent.untap(game); + } + } + } + return true; + } + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/f/ForceBubble.java b/Mage.Sets/src/mage/cards/f/ForceBubble.java new file mode 100644 index 00000000000..1432f5ac6a9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/ForceBubble.java @@ -0,0 +1,141 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.f; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.StateTriggeredAbility; +import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.RemoveAllCountersSourceEffect; +import mage.abilities.effects.common.SacrificeSourceEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.events.DamageEvent; +import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; +import mage.game.permanent.Permanent; + +/** + * + * @author emerald000 & L_J + */ +public class ForceBubble extends CardImpl { + + public ForceBubble(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{2}{W}{W}"); + + // If damage would be dealt to you, put that many depletion counters on Force Bubble instead. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ForceBubbleReplacementEffect())); + + // When there are four or more depletion counters on Force Bubble, sacrifice it. + this.addAbility(new ForceBubbleStateTriggeredAbility()); + + // At the beginning of each end step, remove all depletion counters from Force Bubble. + this.addAbility(new BeginningOfEndStepTriggeredAbility(new RemoveAllCountersSourceEffect(CounterType.DEPLETION), TargetController.ANY, false)); + } + + public ForceBubble(final ForceBubble card) { + super(card); + } + + @Override + public ForceBubble copy() { + return new ForceBubble(this); + } +} + +class ForceBubbleReplacementEffect extends ReplacementEffectImpl { + + ForceBubbleReplacementEffect() { + super(Duration.WhileOnBattlefield, Outcome.PreventDamage); + staticText = "If damage would be dealt to you, put that many depletion counters on {this} instead"; + } + + ForceBubbleReplacementEffect(final ForceBubbleReplacementEffect effect) { + super(effect); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + DamageEvent damageEvent = (DamageEvent) event; + new AddCountersSourceEffect(CounterType.DEPLETION.createInstance(damageEvent.getAmount()), true).apply(game, source); + return true; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == EventType.DAMAGE_PLAYER; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + return event.getTargetId().equals(source.getControllerId()); + } + + @Override + public ForceBubbleReplacementEffect copy() { + return new ForceBubbleReplacementEffect(this); + } +} + +class ForceBubbleStateTriggeredAbility extends StateTriggeredAbility { + + public ForceBubbleStateTriggeredAbility() { + super(Zone.BATTLEFIELD, new SacrificeSourceEffect()); + } + + public ForceBubbleStateTriggeredAbility(final ForceBubbleStateTriggeredAbility ability) { + super(ability); + } + + @Override + public ForceBubbleStateTriggeredAbility copy() { + return new ForceBubbleStateTriggeredAbility(this); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Permanent permanent = game.getPermanent(getSourceId()); + return permanent != null && permanent.getCounters(game).getCount(CounterType.DEPLETION) >= 4; + } + + @Override + public String getRule() { + return "When there are four or more depletion counters on {this}, sacrifice it."; + } +} diff --git a/Mage.Sets/src/mage/cards/f/FrontlineStrategist.java b/Mage.Sets/src/mage/cards/f/FrontlineStrategist.java new file mode 100644 index 00000000000..112b5900dc7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FrontlineStrategist.java @@ -0,0 +1,78 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.f; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.TurnedFaceUpSourceTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.PreventAllDamageByAllPermanentsEffect; +import mage.abilities.keyword.MorphAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Duration; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.SubtypePredicate; + +/** + * + * @author L_J + */ +public class FrontlineStrategist extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("non-Soldier creatures"); + + static { + filter.add(Predicates.not(new SubtypePredicate(SubType.SOLDIER))); + } + + public FrontlineStrategist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{W}"); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Morph {W} + this.addAbility(new MorphAbility(this, new ManaCostsImpl("{W}"))); + // When Frontline Strategist is turned face up, prevent all combat damage non-Soldier creatures would deal this turn. + this.addAbility(new TurnedFaceUpSourceTriggeredAbility(new PreventAllDamageByAllPermanentsEffect(filter, Duration.EndOfTurn, true).setText("prevent all combat damage non-Soldier creatures would deal this turn"))); + } + + public FrontlineStrategist(final FrontlineStrategist card) { + super(card); + } + + @Override + public FrontlineStrategist copy() { + return new FrontlineStrategist(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GeneralJarkeld.java b/Mage.Sets/src/mage/cards/g/GeneralJarkeld.java new file mode 100644 index 00000000000..319a619cf8d --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GeneralJarkeld.java @@ -0,0 +1,211 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.g; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.condition.common.IsStepCondition; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.decorator.ConditionalActivatedAbility; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Outcome; +import mage.constants.SuperType; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.combat.CombatGroup; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetAttackingCreature; + +/** + * + * @author L_J + */ +public class GeneralJarkeld extends CardImpl { + + public GeneralJarkeld(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); + addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // {T}: Switch the blocking creatures of two target attacking creatures. Activate this ability only during the declare blockers step. + Ability ability = new ConditionalActivatedAbility(Zone.BATTLEFIELD, new GeneralJarkeldSwitchBlockersEffect(), new TapSourceCost(), new IsStepCondition(PhaseStep.DECLARE_BLOCKERS, false)); + ability.addTarget(new TargetAttackingCreature(2)); + this.addAbility(ability); + } + + public GeneralJarkeld(final GeneralJarkeld card) { + super(card); + } + + @Override + public GeneralJarkeld copy() { + return new GeneralJarkeld(this); + } + +} + +class GeneralJarkeldSwitchBlockersEffect extends OneShotEffect { + + public GeneralJarkeldSwitchBlockersEffect() { + super(Outcome.Benefit); + this.staticText = "Switch the blocking creatures of two target attacking creatures"; + } + + public GeneralJarkeldSwitchBlockersEffect(final GeneralJarkeldSwitchBlockersEffect effect) { + super(effect); + } + + @Override + public GeneralJarkeldSwitchBlockersEffect copy() { + return new GeneralJarkeldSwitchBlockersEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + List targets = source.getTargets().get(0).getTargets(); + if (controller != null && targets != null) { + Permanent attacker1 = game.getPermanent(targets.get(0)); + Permanent attacker2 = game.getPermanent(targets.get(1)); + if (attacker1 != null && attacker2 != null) { + CombatGroup chosenGroup1 = game.getCombat().findGroup(attacker1.getId()); + CombatGroup chosenGroup2 = game.getCombat().findGroup(attacker2.getId()); + if (chosenGroup1 != null && chosenGroup2 != null) { + Set blockers1 = new HashSet<>(); + Set blockers2 = new HashSet<>(); + Set multiBlockers = new HashSet<>(); + + for (UUID blockerId : chosenGroup1.getBlockers()) { + Permanent blocker = game.getPermanent(blockerId); + if (blocker != null) { + if (blocker.getBlocking() > 1) { + multiBlockers.add(blocker); + } else { + blockers1.add(blocker); + } + } + } + + for (UUID blockerId : chosenGroup2.getBlockers()) { + Permanent blocker = game.getPermanent(blockerId); + if (blocker != null) { + if (blocker.getBlocking() > 1) { + multiBlockers.add(blocker); + } else { + blockers2.add(blocker); + } + } + } + + handleSingleBlockers(blockers1, chosenGroup1, chosenGroup2, controller, game); + handleSingleBlockers(blockers2, chosenGroup2, chosenGroup1, controller, game); + handleMultiBlockers(multiBlockers, chosenGroup1, chosenGroup2, controller, game); + + // the ability doesn't unblock a group that loses all blockers, however it will newly block a previously unblocked group if it gains a blocker this way + if (!(chosenGroup1.getBlockers().isEmpty())) { + chosenGroup1.setBlocked(true); + chosenGroup1.pickBlockerOrder(attacker1.getControllerId(), game); + } + if (!(chosenGroup2.getBlockers().isEmpty())) { + chosenGroup2.setBlocked(true); + chosenGroup2.pickBlockerOrder(attacker2.getControllerId(), game); + } + return true; + } + } + } + return false; + } + + private void handleSingleBlockers(Set blockers, CombatGroup chosenGroup, CombatGroup otherGroup, Player controller, Game game) { + for (Permanent blocker : blockers) { + chosenGroup.remove(blocker.getId()); + blocker.setBlocking(0); + // 10/4/2004 The new blocker does not trigger any abilities which trigger on creatures becoming blockers, because the creatures were already blockers and the simple change of who is blocking does not trigger such abilities. + otherGroup.addBlockerToGroup(blocker.getId(), controller.getId(), game); + } + } + + private void handleMultiBlockers(Set blockers, CombatGroup chosenGroup1, CombatGroup chosenGroup2, Player controller, Game game) { + // for handling multi-blockers (Two Headed Giant of Foriys, etc.) + blockerIteration: + for (Permanent blocker : blockers) { + if (blocker.getBlocking() > 1) { + CombatGroup blockGroup = null; + for (CombatGroup group : game.getCombat().getBlockingGroups()) { + if (group.getBlockers().contains(blocker.getId())) { + blockGroup = group; + break; + } + } + if (blockGroup != null) { + CombatGroup chosenGroup = null; + boolean sameBlocked = false; + for (CombatGroup group : game.getCombat().getGroups()) { + if (group.getBlocked() && group.getBlockers().contains(blocker.getId())) { + if (group == chosenGroup1 || group == chosenGroup2) { + if (sameBlocked) { + continue blockerIteration; + } + sameBlocked = true; + chosenGroup = group; + } + } + } + + if (sameBlocked && chosenGroup != null) { // if none (should not happen) or all the blockers correspond to Jarkeld's targets, the blockers remain the same + CombatGroup otherGroup = (chosenGroup.equals(chosenGroup1) ? chosenGroup2 : chosenGroup1); + chosenGroup.remove(blocker.getId()); + for (UUID attacker : chosenGroup.getAttackers()) { + blockGroup.remove(attacker); + } + otherGroup.addBlockerToGroup(blocker.getId(), controller.getId(), game); + for (UUID attacker : otherGroup.getAttackers()) { + // 10/4/2004 The new blocker does not trigger any abilities which trigger on creatures becoming blockers, because the creatures were already blockers and the simple change of who is blocking does not trigger such abilities. + game.getCombat().addBlockingGroup(blocker.getId(), attacker, controller.getId(), game); + } + blockGroup.pickAttackerOrder(blocker.getControllerId(), game); + } + } + } + } + } +} diff --git a/Mage.Sets/src/mage/cards/g/GraveSifter.java b/Mage.Sets/src/mage/cards/g/GraveSifter.java index df5c373f73e..c60fe13164d 100644 --- a/Mage.Sets/src/mage/cards/g/GraveSifter.java +++ b/Mage.Sets/src/mage/cards/g/GraveSifter.java @@ -95,7 +95,7 @@ class GraveSifterEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Choice typeChoice = new ChoiceCreatureType(); + Choice typeChoice = new ChoiceCreatureType(game.getObject(source.getSourceId())); typeChoice.setMessage("Choose creature type to return cards from your graveyard"); Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { diff --git a/Mage.Sets/src/mage/cards/h/HarshMercy.java b/Mage.Sets/src/mage/cards/h/HarshMercy.java index 931d63a9386..72f42fb3b70 100644 --- a/Mage.Sets/src/mage/cards/h/HarshMercy.java +++ b/Mage.Sets/src/mage/cards/h/HarshMercy.java @@ -96,7 +96,7 @@ class HarshMercyEffect extends OneShotEffect { PlayerIteration: for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) { Player player = game.getPlayer(playerId); - Choice typeChoice = new ChoiceCreatureType(); + Choice typeChoice = new ChoiceCreatureType(sourceObject); while (!player.choose(Outcome.DestroyPermanent, typeChoice, game)) { if (!player.canRespond()) { continue PlayerIteration; diff --git a/Mage.Sets/src/mage/cards/h/HollowOne.java b/Mage.Sets/src/mage/cards/h/HollowOne.java index 3232b899252..f37f64c564b 100644 --- a/Mage.Sets/src/mage/cards/h/HollowOne.java +++ b/Mage.Sets/src/mage/cards/h/HollowOne.java @@ -35,17 +35,15 @@ import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.cost.CostModificationEffectImpl; import mage.abilities.keyword.CyclingAbility; -import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.CostModificationType; import mage.constants.Duration; import mage.constants.Outcome; +import mage.constants.SubType; import mage.constants.Zone; import mage.game.Game; -import mage.players.Player; import mage.util.CardUtil; import mage.watchers.common.CardsCycledOrDiscardedThisTurnWatcher; @@ -94,17 +92,9 @@ class HollowOneReductionEffect extends CostModificationEffectImpl { @Override public boolean apply(Game game, Ability source, Ability abilityToModify) { - Player controller = game.getPlayer(source.getControllerId()); CardsCycledOrDiscardedThisTurnWatcher watcher = (CardsCycledOrDiscardedThisTurnWatcher) game.getState().getWatchers().get(CardsCycledOrDiscardedThisTurnWatcher.class.getSimpleName()); - int reductionAmount = 0; - if (controller != null - && watcher != null) { - for (Card card : watcher.getCardsCycledOrDiscardedThisTurn(controller.getId()).getCards(game)) { - if (card.getOwnerId().equals(controller.getId())) { - reductionAmount++; - } - } - CardUtil.reduceCost(abilityToModify, reductionAmount * 2); + if (watcher != null) { + CardUtil.reduceCost(abilityToModify, watcher.getNumberOfCardsCycledOrDiscardedThisTurn(source.getControllerId()) * 2); return true; } return false; diff --git a/Mage.Sets/src/mage/cards/k/KaronaFalseGod.java b/Mage.Sets/src/mage/cards/k/KaronaFalseGod.java index 56a5c395e37..b287a99d533 100644 --- a/Mage.Sets/src/mage/cards/k/KaronaFalseGod.java +++ b/Mage.Sets/src/mage/cards/k/KaronaFalseGod.java @@ -152,7 +152,7 @@ class KaronaFalseGodEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); MageObject sourceObject = game.getObject(source.getSourceId()); if (sourceObject != null && controller != null) { - Choice typeChoice = new ChoiceCreatureType(); + Choice typeChoice = new ChoiceCreatureType(sourceObject); while (!controller.choose(Outcome.BoostCreature, typeChoice, game)) { if (!controller.canRespond()) { return false; diff --git a/Mage.Sets/src/mage/cards/k/KeldonTwilight.java b/Mage.Sets/src/mage/cards/k/KeldonTwilight.java new file mode 100644 index 00000000000..2b9c9a3ea07 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KeldonTwilight.java @@ -0,0 +1,95 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.k; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.SacrificeEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.ControlledFromStartOfControllerTurnPredicate; +import mage.game.Game; +import mage.watchers.common.AttackedThisTurnWatcher; + +/** + * + * @author spjspj & L_J + */ +public class KeldonTwilight extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("a creature you controlled since the beginning of the turn"); + + static { + filter.add(new ControlledFromStartOfControllerTurnPredicate()); + } + + public KeldonTwilight(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{B}{R}"); + + // At the beginning of each player's end step, if no creatures attacked this turn, that player sacrifices a creature he or she controlled since the beginning of the turn. + Effect effect = new SacrificeEffect(filter, 1, "that player "); + effect.setText("that player sacrifices a creature he or she controlled since the beginning of the turn"); + BeginningOfEndStepTriggeredAbility ability + = new BeginningOfEndStepTriggeredAbility(Zone.BATTLEFIELD, effect, TargetController.ANY, new KeldonTwilightCondition(), false); + this.addAbility(ability, new AttackedThisTurnWatcher()); + } + + public KeldonTwilight(final KeldonTwilight card) { + super(card); + } + + @Override + public KeldonTwilight copy() { + return new KeldonTwilight(this); + } +} + +class KeldonTwilightCondition implements Condition { + + @Override + public boolean apply(Game game, Ability source) { + AttackedThisTurnWatcher watcher = (AttackedThisTurnWatcher) game.getState().getWatchers().get(AttackedThisTurnWatcher.class.getSimpleName()); + if (watcher != null) { + return watcher.getAttackedThisTurnCreatures().isEmpty(); + } + return true; + } + + @Override + public String toString() { + return "if no creatures attacked this turn"; + } + +} diff --git a/Mage.Sets/src/mage/cards/l/LingeringDeath.java b/Mage.Sets/src/mage/cards/l/LingeringDeath.java new file mode 100644 index 00000000000..705c53e3821 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LingeringDeath.java @@ -0,0 +1,119 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.l; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.SacrificeTargetEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.events.GameEvent; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author L_J + */ +public class LingeringDeath extends CardImpl { + + public LingeringDeath(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{B}"); + this.subtype.add(SubType.AURA); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.DestroyPermanent)); + Ability ability = new EnchantAbility(auraTarget.getTargetName()); + this.addAbility(ability); + + // At the beginning of the end step of enchanted creature's controller, that player sacrifices that creature. + this.addAbility(new LingeringDeathAbility()); + } + + public LingeringDeath(final LingeringDeath card) { + super(card); + } + + @Override + public LingeringDeath copy() { + return new LingeringDeath(this); + } +} + +class LingeringDeathAbility extends TriggeredAbilityImpl { + public LingeringDeathAbility() { + super(Zone.BATTLEFIELD, new SacrificeTargetEffect()); + } + + public LingeringDeathAbility(final LingeringDeathAbility ability) { + super(ability); + } + + + @Override + public LingeringDeathAbility copy() { + return new LingeringDeathAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.END_TURN_STEP_PRE; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Permanent enchantment = game.getPermanentOrLKIBattlefield(this.getSourceId()); + if (enchantment != null && enchantment.getAttachedTo() != null) { + Permanent enchantedCreature = game.getPermanent(enchantment.getAttachedTo()); + if (enchantedCreature != null) { + if (event.getPlayerId().equals(enchantedCreature.getControllerId())) { + getEffects().get(0).setTargetPointer(new FixedTarget(enchantment.getAttachedTo())); + return true; + } + } + } + return false; + } + + @Override + public String getRule() { + return "At the beginning of the end step of enchanted creature's controller, that player sacrifices that creature."; + } +} diff --git a/Mage.Sets/src/mage/cards/l/LuminescentRain.java b/Mage.Sets/src/mage/cards/l/LuminescentRain.java index 5437d5eec37..cb881e2b5e0 100644 --- a/Mage.Sets/src/mage/cards/l/LuminescentRain.java +++ b/Mage.Sets/src/mage/cards/l/LuminescentRain.java @@ -86,9 +86,9 @@ class LuminescentRainEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); + Player player = game.getPlayer(source.getControllerId()); if (player != null) { - Choice typeChoice = new ChoiceCreatureType(); + Choice typeChoice = new ChoiceCreatureType(game.getObject(source.getSourceId())); while (!player.choose(Outcome.BoostCreature, typeChoice, game)) { if (!player.canRespond()) { return false; diff --git a/Mage.Sets/src/mage/cards/m/MistformSliver.java b/Mage.Sets/src/mage/cards/m/MistformSliver.java index 2ac7c3b16e3..2364d2cd9c4 100644 --- a/Mage.Sets/src/mage/cards/m/MistformSliver.java +++ b/Mage.Sets/src/mage/cards/m/MistformSliver.java @@ -92,7 +92,7 @@ class MistformSliverEffect extends OneShotEffect { Player player = game.getPlayer(source.getControllerId()); Permanent permanent = game.getPermanent(source.getSourceId()); if (player != null && permanent != null) { - Choice typeChoice = new ChoiceCreatureType(); + Choice typeChoice = new ChoiceCreatureType(permanent); while (!player.choose(Outcome.Detriment, typeChoice, game)) { if (!player.canRespond()) { return false; diff --git a/Mage.Sets/src/mage/cards/n/NewBlood.java b/Mage.Sets/src/mage/cards/n/NewBlood.java index cd2c534219a..a6f3905c73e 100644 --- a/Mage.Sets/src/mage/cards/n/NewBlood.java +++ b/Mage.Sets/src/mage/cards/n/NewBlood.java @@ -39,7 +39,7 @@ import mage.abilities.text.TextPartSubType; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.choices.Choice; -import mage.choices.ChoiceImpl; +import mage.choices.ChoiceCreatureType; import mage.constants.*; import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.predicate.Predicates; @@ -145,9 +145,8 @@ class ChangeCreatureTypeTargetEffect extends ContinuousEffectImpl { return; } if (fromSubType == null) { - Choice typeChoice = new ChoiceImpl(true); + Choice typeChoice = new ChoiceCreatureType(game.getObject(source.getSourceId())); typeChoice.setMessage("Choose creature type to change to Vampire"); - typeChoice.setChoices(SubType.getCreatureTypes(false).stream().map(SubType::toString).collect(Collectors.toCollection(LinkedHashSet::new))); while (!controller.choose(outcome, typeChoice, game)) { if (!controller.canRespond()) { return; diff --git a/Mage.Sets/src/mage/cards/n/NykthosShrineToNyx.java b/Mage.Sets/src/mage/cards/n/NykthosShrineToNyx.java index ac2ea819797..c6031c4c90c 100644 --- a/Mage.Sets/src/mage/cards/n/NykthosShrineToNyx.java +++ b/Mage.Sets/src/mage/cards/n/NykthosShrineToNyx.java @@ -94,7 +94,7 @@ class NykthosShrineToNyxManaAbility extends ActivatedManaAbilityImpl { public List getNetMana(Game game) { netMana.clear(); if (game != null) { - for (String colorChoice : ChoiceColor.colorChoices) { + for (String colorChoice : ChoiceColor.getBaseColors()) { netMana.add(((NykthosDynamicManaEffect) this.getEffects().get(0)).computeMana(colorChoice, game, this)); } } diff --git a/Mage.Sets/src/mage/cards/o/OrimsCure.java b/Mage.Sets/src/mage/cards/o/OrimsCure.java new file mode 100644 index 00000000000..0eb663bcf08 --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OrimsCure.java @@ -0,0 +1,84 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.o; + +import java.util.UUID; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.costs.AlternativeCostSourceAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.common.TapTargetCost; +import mage.abilities.effects.common.PreventDamageToTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Duration; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.target.common.TargetControlledPermanent; +import mage.target.common.TargetCreatureOrPlayer; + +/** + * + * @author L_J + */ +public class OrimsCure extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("If you control a Plains"); + private static final FilterControlledCreaturePermanent filterCreature = new FilterControlledCreaturePermanent("untapped creature you control"); + + static { + filter.add(new SubtypePredicate(SubType.PLAINS)); + filterCreature.add(Predicates.not(new TappedPredicate())); + } + + public OrimsCure(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{1}{W}"); + + // If you control a Plains, you may tap an untapped creature you control rather than pay Orim's Cure's mana cost. + Cost cost = new TapTargetCost(new TargetControlledPermanent(1,1,filterCreature,false)); + cost.setText(" tap an untapped creature you control"); + this.addAbility(new AlternativeCostSourceAbility(cost, new PermanentsOnTheBattlefieldCondition(filter))); + + // Prevent the next 4 damage that would be dealt to target creature or player this turn. + this.getSpellAbility().addEffect(new PreventDamageToTargetEffect(Duration.EndOfTurn, 4)); + this.getSpellAbility().addTarget(new TargetCreatureOrPlayer()); + } + + public OrimsCure(final OrimsCure card) { + super(card); + } + + @Override + public OrimsCure copy() { + return new OrimsCure(this); + } +} diff --git a/Mage.Sets/src/mage/cards/o/Outbreak.java b/Mage.Sets/src/mage/cards/o/Outbreak.java index 1b6e48355a1..4de53cd1150 100644 --- a/Mage.Sets/src/mage/cards/o/Outbreak.java +++ b/Mage.Sets/src/mage/cards/o/Outbreak.java @@ -96,7 +96,7 @@ class OutbreakEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); if (player != null) { - Choice typeChoice = new ChoiceCreatureType(); + Choice typeChoice = new ChoiceCreatureType(game.getObject(source.getSourceId())); while (!player.choose(outcome, typeChoice, game)) { if (!player.canRespond()) { return false; diff --git a/Mage.Sets/src/mage/cards/p/PacksDisdain.java b/Mage.Sets/src/mage/cards/p/PacksDisdain.java index 8b98c53a0eb..88c1951e0b3 100644 --- a/Mage.Sets/src/mage/cards/p/PacksDisdain.java +++ b/Mage.Sets/src/mage/cards/p/PacksDisdain.java @@ -93,7 +93,7 @@ class PacksDisdainEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); if (player != null) { - Choice typeChoice = new ChoiceCreatureType(); + Choice typeChoice = new ChoiceCreatureType(game.getObject(source.getSourceId())); while (!player.choose(Outcome.UnboostCreature, typeChoice, game)) { if (!player.canRespond()) { return false; diff --git a/Mage.Sets/src/mage/cards/p/PatriarchsBidding.java b/Mage.Sets/src/mage/cards/p/PatriarchsBidding.java index a964d7f791c..e4500783854 100644 --- a/Mage.Sets/src/mage/cards/p/PatriarchsBidding.java +++ b/Mage.Sets/src/mage/cards/p/PatriarchsBidding.java @@ -93,7 +93,7 @@ class PatriarchsBiddingEffect extends OneShotEffect { Set chosenTypes = new HashSet<>(); for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) { Player player = game.getPlayer(playerId); - Choice typeChoice = new ChoiceCreatureType(); + Choice typeChoice = new ChoiceCreatureType(sourceObject); while (!player.choose(Outcome.PutCreatureInPlay, typeChoice, game)) { if (!player.canRespond()) { break; diff --git a/Mage.Sets/src/mage/cards/p/PatronOfTheNezumi.java b/Mage.Sets/src/mage/cards/p/PatronOfTheNezumi.java index bad292df09b..710bad43f7c 100644 --- a/Mage.Sets/src/mage/cards/p/PatronOfTheNezumi.java +++ b/Mage.Sets/src/mage/cards/p/PatronOfTheNezumi.java @@ -28,12 +28,12 @@ package mage.cards.p; import java.util.UUID; + import mage.MageInt; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.abilities.effects.common.LoseLifeTargetEffect; import mage.abilities.keyword.OfferingAbility; -import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -44,6 +44,7 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.GameEvent.EventType; import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; import mage.target.targetpointer.FixedTarget; @@ -96,10 +97,9 @@ class PatronOfTheNezumiTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { ZoneChangeEvent zEvent = (ZoneChangeEvent) event; - if (zEvent.getFromZone() == Zone.BATTLEFIELD - && zEvent.getToZone() == Zone.GRAVEYARD) { - Card card = game.getCard(zEvent.getTargetId()); - if (card != null && game.getOpponents(controllerId).contains(card.getOwnerId())) { + if (zEvent.isDiesEvent()) { + Permanent permanent = game.getPermanentOrLKIBattlefield(zEvent.getTargetId()); + if (permanent != null && game.getOpponents(controllerId).contains(permanent.getOwnerId())) { this.getEffects().get(0).setTargetPointer(new FixedTarget(zEvent.getPlayerId())); return true; } diff --git a/Mage.Sets/src/mage/cards/p/PeerPressure.java b/Mage.Sets/src/mage/cards/p/PeerPressure.java index 8101dc06be5..4ea1cfe77c3 100644 --- a/Mage.Sets/src/mage/cards/p/PeerPressure.java +++ b/Mage.Sets/src/mage/cards/p/PeerPressure.java @@ -93,7 +93,7 @@ class PeerPressureEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - Choice choice = new ChoiceCreatureType(); + Choice choice = new ChoiceCreatureType(game.getObject(source.getSourceId())); while (!controller.choose(Outcome.GainControl, choice, game)) { if (!controller.canRespond()) { return false; diff --git a/Mage.Sets/src/mage/cards/r/ReveredElder.java b/Mage.Sets/src/mage/cards/r/ReveredElder.java new file mode 100644 index 00000000000..e2f0370c2e6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/ReveredElder.java @@ -0,0 +1,67 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.r; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.PreventDamageToSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Duration; +import mage.constants.Zone; + +/** + * + * @author L_J + */ +public class ReveredElder extends CardImpl { + + public ReveredElder(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{W}"); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CLERIC); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // {1}: Prevent the next 1 damage that would be dealt to Revered Elder this turn. + this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new PreventDamageToSourceEffect(Duration.EndOfTurn, 1), new ManaCostsImpl("{1}"))); + } + + public ReveredElder(final ReveredElder card) { + super(card); + } + + @Override + public ReveredElder copy() { + return new ReveredElder(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RiptideChronologist.java b/Mage.Sets/src/mage/cards/r/RiptideChronologist.java index b4a838bf6a6..c4d0b45ad60 100644 --- a/Mage.Sets/src/mage/cards/r/RiptideChronologist.java +++ b/Mage.Sets/src/mage/cards/r/RiptideChronologist.java @@ -94,7 +94,7 @@ class RiptideChronologistEffect extends OneShotEffect { Player player = game.getPlayer(source.getControllerId()); MageObject sourceObject = game.getObject(source.getSourceId()); if (player != null) { - Choice typeChoice = new ChoiceCreatureType(); + Choice typeChoice = new ChoiceCreatureType(sourceObject); while (!player.choose(outcome, typeChoice, game)) { if (!player.canRespond()) { return false; diff --git a/Mage.Sets/src/mage/cards/r/RiptideShapeshifter.java b/Mage.Sets/src/mage/cards/r/RiptideShapeshifter.java index 5a5d21a2b94..ec5ee0e3049 100644 --- a/Mage.Sets/src/mage/cards/r/RiptideShapeshifter.java +++ b/Mage.Sets/src/mage/cards/r/RiptideShapeshifter.java @@ -96,7 +96,7 @@ class RiptideShapeshifterEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); MageObject sourceObject = source.getSourceObject(game); if (controller != null && sourceObject != null) { - Choice choice = new ChoiceCreatureType(); + Choice choice = new ChoiceCreatureType(sourceObject); while (!controller.choose(Outcome.BoostCreature, choice, game)) { if (!controller.canRespond()) { return false; diff --git a/Mage.Sets/src/mage/cards/r/RoarOfTheCrowd.java b/Mage.Sets/src/mage/cards/r/RoarOfTheCrowd.java index 181d0fa4d95..213b9198079 100644 --- a/Mage.Sets/src/mage/cards/r/RoarOfTheCrowd.java +++ b/Mage.Sets/src/mage/cards/r/RoarOfTheCrowd.java @@ -89,7 +89,7 @@ class RoarOfTheCrowdEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); if (player != null) { - Choice typeChoice = new ChoiceCreatureType(); + Choice typeChoice = new ChoiceCreatureType(game.getObject(source.getSourceId())); while (!player.choose(Outcome.LoseLife, typeChoice, game)) { if (!player.canRespond()) { return false; diff --git a/Mage.Sets/src/mage/cards/s/SorrowsPath.java b/Mage.Sets/src/mage/cards/s/SorrowsPath.java new file mode 100644 index 00000000000..2a74e2bede7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SorrowsPath.java @@ -0,0 +1,197 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.s; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.BecomesTappedSourceTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DamageAllEffect; +import mage.abilities.effects.common.DamageControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterOpponentsCreaturePermanent; +import mage.filter.predicate.permanent.BlockingPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.combat.CombatGroup; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCreaturePermanentSameController; + +/** + * + * @author L_J + */ +public class SorrowsPath extends CardImpl { + + private static final FilterOpponentsCreaturePermanent filter = new FilterOpponentsCreaturePermanent("blocking creatures an opponent controls"); + + static { + filter.add(new BlockingPredicate()); + } + + public SorrowsPath(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + // {T}: Choose two target blocking creatures an opponent controls. If each of those creatures could block all creatures that the other is blocking, remove both of them from combat. Each one then blocks all creatures the other was blocking. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new SorrowsPathSwitchBlockersEffect(), new TapSourceCost()); + ability.addTarget(new TargetCreaturePermanentSameController(2, 2, filter, false)); + this.addAbility(ability); + + // Whenever Sorrow's Path becomes tapped, it deals 2 damage to you and each creature you control. + Ability ability2 = new BecomesTappedSourceTriggeredAbility(new DamageControllerEffect(2)); + ability2.addEffect(new DamageAllEffect(2, new FilterControlledCreaturePermanent()).setText("and each creature you control")); + this.addAbility(ability2); + } + + public SorrowsPath(final SorrowsPath card) { + super(card); + } + + @Override + public SorrowsPath copy() { + return new SorrowsPath(this); + } + +} + +class SorrowsPathSwitchBlockersEffect extends OneShotEffect { + + public SorrowsPathSwitchBlockersEffect() { + super(Outcome.Detriment); + this.staticText = "Choose two target blocking creatures an opponent controls. If each of those creatures could block all creatures that the other is blocking, remove both of them from combat. Each one then blocks all creatures the other was blocking"; + } + + public SorrowsPathSwitchBlockersEffect(final SorrowsPathSwitchBlockersEffect effect) { + super(effect); + } + + @Override + public SorrowsPathSwitchBlockersEffect copy() { + return new SorrowsPathSwitchBlockersEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + List targets = source.getTargets().get(0).getTargets(); + if (controller != null && targets != null) { + Permanent blocker1 = game.getPermanent(targets.get(0)); + Permanent blocker2 = game.getPermanent(targets.get(1)); + if (blocker1 != null && blocker2 != null) { + Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); + // 10/1/2009: When determining whether a creature could block all creatures the other is blocking, take into account evasion abilities (like flying), protection + // abilities, and other blocking restrictions, as well as abilities that allow a creature to block multiple creatures or block as though a certain condition were true. + // Take into account whether those creatures are tapped, but not whether they have costs to block (since those apply only as blockers are declared). + CombatGroup chosenGroup1 = findBlockingGroup(blocker1, game); + CombatGroup chosenGroup2 = findBlockingGroup(blocker2, game); + Set attackers1 = new HashSet<>(); + Set attackers2 = new HashSet<>(); + boolean blockPossible = false; + + if (chosenGroup1 != null && chosenGroup2 != null) { + blockPossible = getRestrictions(chosenGroup1, blocker2, attackers1, sourcePermanent, controller, game) && getRestrictions(chosenGroup2, blocker1, attackers2, sourcePermanent, controller, game); + } + if (!blockPossible) { + return true; + } + + // 10/1/2009: When the first ability resolves, if all the creatures that one of the targeted creatures was blocking have left combat, then the other targeted creature + // is considered to be able to block all creatures the first creature is blocking. If the ability has its full effect, the second creature will be removed from combat + // but not returned to combat; it doesn't block anything. + game.getCombat().removeFromCombat(blocker1.getId(), game, false); + game.getCombat().removeFromCombat(blocker2.getId(), game, false); + blocker1.setRemovedFromCombat(attackers2.isEmpty()); + blocker2.setRemovedFromCombat(attackers1.isEmpty()); + + // 10/1/2009: Abilities that trigger whenever one of the targeted creatures blocks will trigger when the first ability resolves, because those creatures will change from + // not blocking (since they're removed from combat) to blocking. It doesn't matter if those abilities triggered when those creatures blocked the first time. Abilities + // that trigger whenever one of the attacking creatures becomes blocked will not trigger again, because they never stopped being blocked creatures. Abilities that + // trigger whenever a creature blocks one of the attacking creatures will trigger again, though; those kinds of abilities trigger once for each creature that blocks. + reassignBlocker(blocker1, attackers2, game); + reassignBlocker(blocker2, attackers1, game); + return true; + } + } + return false; + } + + private CombatGroup findBlockingGroup(Permanent blocker, Game game) { + if (blocker.getBlocking() > 1) { + for (CombatGroup group : game.getCombat().getBlockingGroups()) { + if (group.getBlockers().contains(blocker.getId())) { + return group; + } + } + } + return game.getCombat().findGroupOfBlocker(blocker.getId()); + } + + private boolean getRestrictions(CombatGroup chosenGroup, Permanent blocker, Set attackers, Permanent sourcePermanent, Player controller, Game game) { + for (UUID attackerId : chosenGroup.getAttackers()) { + Permanent attacker = game.getPermanent(attackerId); + if (attacker != null) { + if (blocker.canBlock(attackerId, game) && (blocker.getMaxBlocks() == 0 || chosenGroup.getAttackers().size() <= blocker.getMaxBlocks())) { + attackers.add(attacker); + } else { + game.informPlayer(controller, "Illegal block detected (" + blocker.getName() + "), effect of " + sourcePermanent.getName() + " doesn't apply."); + return false; + } + } + } + return true; + } + + private void reassignBlocker(Permanent blocker, Set attackers, Game game) { + for (Permanent attacker : attackers) { + CombatGroup group = game.getCombat().findGroup(attacker.getId()); + if (group != null) { + group.addBlockerToGroup(blocker.getId(), blocker.getControllerId(), game); + game.getCombat().addBlockingGroup(blocker.getId(), attacker.getId(), blocker.getControllerId(), game); + // TODO: find an alternate event solution for multi-blockers (as per issue #4285), this will work fine for single blocker creatures though + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.BLOCKER_DECLARED, attacker.getId(), blocker.getId(), blocker.getControllerId())); + group.pickBlockerOrder(attacker.getControllerId(), game); + } + } + CombatGroup blockGroup = findBlockingGroup(blocker, game); // a new blockingGroup is formed, so it's necessary to find it again + if (blockGroup != null) { + blockGroup.pickAttackerOrder(blocker.getControllerId(), game); + } + } + +} diff --git a/Mage.Sets/src/mage/cards/s/Standardize.java b/Mage.Sets/src/mage/cards/s/Standardize.java index b34c7065372..6a1adee518a 100644 --- a/Mage.Sets/src/mage/cards/s/Standardize.java +++ b/Mage.Sets/src/mage/cards/s/Standardize.java @@ -85,7 +85,7 @@ class StandardizeEffect extends OneShotEffect { MageObject sourceObject = game.getObject(source.getSourceId()); String chosenType = ""; if (player != null && sourceObject != null) { - Choice typeChoice = new ChoiceCreatureType(); + Choice typeChoice = new ChoiceCreatureType(sourceObject); typeChoice.setMessage("Choose a creature type other than Wall"); typeChoice.getChoices().remove("Wall"); while (!player.choose(Outcome.BoostCreature, typeChoice, game)) { diff --git a/Mage.Sets/src/mage/cards/t/ToxinSliver.java b/Mage.Sets/src/mage/cards/t/ToxinSliver.java index 36cfe08f38b..a8259c7ea57 100644 --- a/Mage.Sets/src/mage/cards/t/ToxinSliver.java +++ b/Mage.Sets/src/mage/cards/t/ToxinSliver.java @@ -45,7 +45,7 @@ import mage.filter.common.FilterCreaturePermanent; public class ToxinSliver extends CardImpl { public ToxinSliver(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); this.subtype.add(SubType.SLIVER); this.power = new MageInt(3); @@ -53,9 +53,9 @@ public class ToxinSliver extends CardImpl { // Whenever a Sliver deals combat damage to a creature, destroy that creature. It can't be regenerated. this.addAbility(new DealsDamageToACreatureAllTriggeredAbility( - new DestroyTargetEffect(true), false, - new FilterCreaturePermanent(SubType.SLIVER,"a Sliver"), - SetTargetPointer.PERMANENT, true)); + new DestroyTargetEffect(true), false, + new FilterCreaturePermanent(SubType.SLIVER, "a Sliver"), + SetTargetPointer.PERMANENT_TARGET, true)); } diff --git a/Mage.Sets/src/mage/cards/t/TribalUnity.java b/Mage.Sets/src/mage/cards/t/TribalUnity.java index e3ac24efd3f..fc9653ce1f1 100644 --- a/Mage.Sets/src/mage/cards/t/TribalUnity.java +++ b/Mage.Sets/src/mage/cards/t/TribalUnity.java @@ -91,7 +91,7 @@ class TribalUnityEffect extends OneShotEffect { MageObject sourceObject = game.getObject(source.getSourceId()); int boost = amount.calculate(game, source, this); if (player != null) { - Choice typeChoice = new ChoiceCreatureType(); + Choice typeChoice = new ChoiceCreatureType(sourceObject); while (!player.choose(outcome, typeChoice, game)) { if (!player.canRespond()) { return false; diff --git a/Mage.Sets/src/mage/cards/t/TsabosDecree.java b/Mage.Sets/src/mage/cards/t/TsabosDecree.java index 36aeb9acc35..b455b455054 100644 --- a/Mage.Sets/src/mage/cards/t/TsabosDecree.java +++ b/Mage.Sets/src/mage/cards/t/TsabosDecree.java @@ -90,7 +90,7 @@ class TsabosDecreeEffect extends OneShotEffect { Player targetPlayer = game.getPlayer(targetPointer.getFirst(game, source)); MageObject sourceObject = game.getObject(source.getSourceId()); if (player != null) { - Choice typeChoice = new ChoiceCreatureType(); + Choice typeChoice = new ChoiceCreatureType(sourceObject); while (!player.choose(outcome, typeChoice, game)) { if (!player.canRespond()) { return false; diff --git a/Mage.Sets/src/mage/cards/w/WalkingDesecration.java b/Mage.Sets/src/mage/cards/w/WalkingDesecration.java index 54988c4923a..f23339c4e2a 100644 --- a/Mage.Sets/src/mage/cards/w/WalkingDesecration.java +++ b/Mage.Sets/src/mage/cards/w/WalkingDesecration.java @@ -91,7 +91,7 @@ class WalkingDesecrationEffect extends OneShotEffect { Player player = game.getPlayer(source.getControllerId()); MageObject sourceObject = game.getObject(source.getSourceId()); if (player != null) { - Choice typeChoice = new ChoiceCreatureType(); + Choice typeChoice = new ChoiceCreatureType(sourceObject); while (!player.choose(outcome, typeChoice, game)) { if (!player.canRespond()) { return false; diff --git a/Mage.Sets/src/mage/cards/w/Warpath.java b/Mage.Sets/src/mage/cards/w/Warpath.java new file mode 100644 index 00000000000..c48bcb5eabc --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/Warpath.java @@ -0,0 +1,69 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.w; + +import java.util.UUID; +import mage.abilities.effects.common.DamageAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.permanent.BlockedPredicate; +import mage.filter.predicate.permanent.BlockingPredicate; + +/** + * + * @author L_J + */ +public class Warpath extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("blocking creature and each blocked creature"); + + static { + filter.add(Predicates.or( + new BlockingPredicate(), + new BlockedPredicate())); + } + + public Warpath(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{3}{R}"); + + // Warpath deals 3 damage to each blocking creature and each blocked creature. + this.getSpellAbility().addEffect(new DamageAllEffect(3, filter)); + } + + public Warpath(final Warpath card) { + super(card); + } + + @Override + public Warpath copy() { + return new Warpath(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WarrenWeirding.java b/Mage.Sets/src/mage/cards/w/WarrenWeirding.java index 7942e453cf7..bc205a14bb8 100644 --- a/Mage.Sets/src/mage/cards/w/WarrenWeirding.java +++ b/Mage.Sets/src/mage/cards/w/WarrenWeirding.java @@ -105,7 +105,7 @@ class WarrenWeirdingEffect extends OneShotEffect { FilterControlledPermanent filter = new FilterControlledPermanent("creature"); filter.add(new CardTypePredicate(CardType.CREATURE)); filter.add(new ControllerIdPredicate(player.getId())); - TargetControlledPermanent target = new TargetControlledPermanent(1, 1, filter, false); + TargetControlledPermanent target = new TargetControlledPermanent(1, 1, filter, true); //A spell or ability could have removed the only legal target this player //had, if thats the case this ability should fizzle. diff --git a/Mage.Sets/src/mage/sets/IceAge.java b/Mage.Sets/src/mage/sets/IceAge.java index 8e752f504bf..1e47953dc1e 100644 --- a/Mage.Sets/src/mage/sets/IceAge.java +++ b/Mage.Sets/src/mage/sets/IceAge.java @@ -147,6 +147,7 @@ public class IceAge extends ExpansionSet { cards.add(new SetCardInfo("Fyndhorn Pollen", 133, Rarity.RARE, mage.cards.f.FyndhornPollen.class)); cards.add(new SetCardInfo("Game of Chaos", 186, Rarity.RARE, mage.cards.g.GameOfChaos.class)); cards.add(new SetCardInfo("Gangrenous Zombies", 15, Rarity.COMMON, mage.cards.g.GangrenousZombies.class)); + cards.add(new SetCardInfo("General Jarkeld", 251, Rarity.RARE, mage.cards.g.GeneralJarkeld.class)); cards.add(new SetCardInfo("Giant Growth", 134, Rarity.COMMON, mage.cards.g.GiantGrowth.class)); cards.add(new SetCardInfo("Giant Trap Door Spider", 371, Rarity.UNCOMMON, mage.cards.g.GiantTrapDoorSpider.class)); cards.add(new SetCardInfo("Glacial Chasm", 331, Rarity.UNCOMMON, mage.cards.g.GlacialChasm.class)); diff --git a/Mage.Sets/src/mage/sets/MastersEditionIII.java b/Mage.Sets/src/mage/sets/MastersEditionIII.java index 947551534fe..540f53d38cc 100644 --- a/Mage.Sets/src/mage/sets/MastersEditionIII.java +++ b/Mage.Sets/src/mage/sets/MastersEditionIII.java @@ -220,6 +220,7 @@ public class MastersEditionIII extends ExpansionSet { cards.add(new SetCardInfo("Sivitri Scarzam", 175, Rarity.COMMON, mage.cards.s.SivitriScarzam.class)); cards.add(new SetCardInfo("Slashing Tiger", 133, Rarity.COMMON, mage.cards.s.SlashingTiger.class)); cards.add(new SetCardInfo("Sol Grail", 201, Rarity.COMMON, mage.cards.s.SolGrail.class)); + cards.add(new SetCardInfo("Sorrow's Path", 211, Rarity.RARE, mage.cards.s.SorrowsPath.class)); cards.add(new SetCardInfo("Spirit Shackle", 74, Rarity.COMMON, mage.cards.s.SpiritShackle.class)); cards.add(new SetCardInfo("Spoils of Victory", 134, Rarity.COMMON, mage.cards.s.SpoilsOfVictory.class)); cards.add(new SetCardInfo("Stolen Grain", 75, Rarity.UNCOMMON, mage.cards.s.StolenGrain.class)); diff --git a/Mage.Sets/src/mage/sets/MercadianMasques.java b/Mage.Sets/src/mage/sets/MercadianMasques.java index df71e3b1794..e61b95875c7 100644 --- a/Mage.Sets/src/mage/sets/MercadianMasques.java +++ b/Mage.Sets/src/mage/sets/MercadianMasques.java @@ -111,6 +111,7 @@ public class MercadianMasques extends ExpansionSet { cards.add(new SetCardInfo("Counterspell", 69, Rarity.COMMON, mage.cards.c.Counterspell.class)); cards.add(new SetCardInfo("Cowardice", 70, Rarity.RARE, mage.cards.c.Cowardice.class)); cards.add(new SetCardInfo("Crackdown", 15, Rarity.RARE, mage.cards.c.Crackdown.class)); + cards.add(new SetCardInfo("Crag Saurian", 185, Rarity.RARE, mage.cards.c.CragSaurian.class)); cards.add(new SetCardInfo("Crash", 186, Rarity.COMMON, mage.cards.c.Crash.class)); cards.add(new SetCardInfo("Credit Voucher", 289, Rarity.UNCOMMON, mage.cards.c.CreditVoucher.class)); cards.add(new SetCardInfo("Crenellated Wall", 290, Rarity.UNCOMMON, mage.cards.c.CrenellatedWall.class)); @@ -237,6 +238,7 @@ public class MercadianMasques extends ExpansionSet { cards.add(new SetCardInfo("Noble Purpose", 32, Rarity.UNCOMMON, mage.cards.n.NoblePurpose.class)); cards.add(new SetCardInfo("Notorious Assassin", 150, Rarity.RARE, mage.cards.n.NotoriousAssassin.class)); cards.add(new SetCardInfo("Ogre Taskmaster", 206, Rarity.UNCOMMON, mage.cards.o.OgreTaskmaster.class)); + cards.add(new SetCardInfo("Orim's Cure", 33, Rarity.COMMON, mage.cards.o.OrimsCure.class)); cards.add(new SetCardInfo("Overtaker", 89, Rarity.RARE, mage.cards.o.Overtaker.class)); cards.add(new SetCardInfo("Panacea", 308, Rarity.UNCOMMON, mage.cards.p.Panacea.class)); cards.add(new SetCardInfo("Pangosaur", 261, Rarity.RARE, mage.cards.p.Pangosaur.class)); @@ -263,6 +265,7 @@ public class MercadianMasques extends ExpansionSet { cards.add(new SetCardInfo("Rappelling Scouts", 41, Rarity.RARE, mage.cards.r.RappellingScouts.class)); cards.add(new SetCardInfo("Remote Farm", 323, Rarity.COMMON, mage.cards.r.RemoteFarm.class)); cards.add(new SetCardInfo("Renounce", 42, Rarity.UNCOMMON, mage.cards.r.Renounce.class)); + cards.add(new SetCardInfo("Revered Elder", 43, Rarity.COMMON, mage.cards.r.ReveredElder.class)); cards.add(new SetCardInfo("Reverent Mantra", 44, Rarity.RARE, mage.cards.r.ReverentMantra.class)); cards.add(new SetCardInfo("Revive", 262, Rarity.UNCOMMON, mage.cards.r.Revive.class)); cards.add(new SetCardInfo("Righteous Aura", 45, Rarity.UNCOMMON, mage.cards.r.RighteousAura.class)); @@ -362,6 +365,7 @@ public class MercadianMasques extends ExpansionSet { cards.add(new SetCardInfo("War Cadence", 224, Rarity.UNCOMMON, mage.cards.w.WarCadence.class)); cards.add(new SetCardInfo("War Tax", 113, Rarity.UNCOMMON, mage.cards.w.WarTax.class)); cards.add(new SetCardInfo("Warmonger", 225, Rarity.UNCOMMON, mage.cards.w.Warmonger.class)); + cards.add(new SetCardInfo("Warpath", 226, Rarity.UNCOMMON, mage.cards.w.Warpath.class)); cards.add(new SetCardInfo("Waterfront Bouncer", 114, Rarity.COMMON, mage.cards.w.WaterfrontBouncer.class)); cards.add(new SetCardInfo("Wave of Reckoning", 56, Rarity.RARE, mage.cards.w.WaveOfReckoning.class)); cards.add(new SetCardInfo("Wild Jhovall", 227, Rarity.COMMON, mage.cards.w.WildJhovall.class)); diff --git a/Mage.Sets/src/mage/sets/MerfolkVsGoblins.java b/Mage.Sets/src/mage/sets/MerfolkVsGoblins.java index caab14c36ca..de3ef4866ae 100644 --- a/Mage.Sets/src/mage/sets/MerfolkVsGoblins.java +++ b/Mage.Sets/src/mage/sets/MerfolkVsGoblins.java @@ -28,6 +28,7 @@ package mage.sets; import mage.cards.ExpansionSet; +import mage.constants.Rarity; import mage.constants.SetType; /** @@ -46,5 +47,69 @@ public class MerfolkVsGoblins extends ExpansionSet { super("Duel Decks: Merfolk vs. Goblins", "DDT", ExpansionSet.buildDate(2017, 11, 10), SetType.SUPPLEMENTAL); this.blockName = "Duel Decks"; this.hasBasicLands = false; + + cards.add(new SetCardInfo("Aquitect's Will", 2, Rarity.COMMON, mage.cards.a.AquitectsWill.class)); + cards.add(new SetCardInfo("Battle Squadron", 33, Rarity.UNCOMMON, mage.cards.b.BattleSquadron.class)); + cards.add(new SetCardInfo("Blighted Cataract", 26, Rarity.UNCOMMON, mage.cards.b.BlightedCataract.class)); + cards.add(new SetCardInfo("Blighted Gorge", 58, Rarity.UNCOMMON, mage.cards.b.BlightedGorge.class)); + cards.add(new SetCardInfo("Boggart Brute", 34, Rarity.COMMON, mage.cards.b.BoggartBrute.class)); + cards.add(new SetCardInfo("Brittle Effigy", 56, Rarity.RARE, mage.cards.b.BrittleEffigy.class)); + cards.add(new SetCardInfo("Brute Strength", 35, Rarity.COMMON, mage.cards.b.BruteStrength.class)); + cards.add(new SetCardInfo("Claustrophobia", 3, Rarity.COMMON, mage.cards.c.Claustrophobia.class)); + cards.add(new SetCardInfo("Cleaver Riot", 36, Rarity.UNCOMMON, mage.cards.c.CleaverRiot.class)); + cards.add(new SetCardInfo("Cold-Eyed Selkie", 25, Rarity.RARE, mage.cards.c.ColdEyedSelkie.class)); + cards.add(new SetCardInfo("Concentrate", 4, Rarity.UNCOMMON, mage.cards.c.Concentrate.class)); + cards.add(new SetCardInfo("Ember Hauler", 37, Rarity.UNCOMMON, mage.cards.e.EmberHauler.class)); + cards.add(new SetCardInfo("Engulf the Shore", 5, Rarity.RARE, mage.cards.e.EngulfTheShore.class)); + cards.add(new SetCardInfo("Essence Scatter", 6, Rarity.COMMON, mage.cards.e.EssenceScatter.class)); + cards.add(new SetCardInfo("Forgotten Cave", 59, Rarity.COMMON, mage.cards.f.ForgottenCave.class)); + cards.add(new SetCardInfo("Foundry Street Denizen", 38, Rarity.COMMON, mage.cards.f.FoundryStreetDenizen.class)); + cards.add(new SetCardInfo("Gempalm Incinerator", 39, Rarity.UNCOMMON, mage.cards.g.GempalmIncinerator.class)); + cards.add(new SetCardInfo("Ghostfire", 40, Rarity.COMMON, mage.cards.g.Ghostfire.class)); + cards.add(new SetCardInfo("Goblin Charbelcher", 57, Rarity.RARE, mage.cards.g.GoblinCharbelcher.class)); + cards.add(new SetCardInfo("Goblin Chieftain", 41, Rarity.RARE, mage.cards.g.GoblinChieftain.class)); + cards.add(new SetCardInfo("Goblin Diplomats", 42, Rarity.RARE, mage.cards.g.GoblinDiplomats.class)); + cards.add(new SetCardInfo("Goblin Glory Chaser", 43, Rarity.UNCOMMON, mage.cards.g.GoblinGloryChaser.class)); + cards.add(new SetCardInfo("Goblin Goon", 44, Rarity.RARE, mage.cards.g.GoblinGoon.class)); + cards.add(new SetCardInfo("Goblin Grenade", 45, Rarity.UNCOMMON, mage.cards.g.GoblinGrenade.class)); + cards.add(new SetCardInfo("Goblin Rabblemaster", 46, Rarity.RARE, mage.cards.g.GoblinRabblemaster.class)); + cards.add(new SetCardInfo("Goblin Razerunners", 47, Rarity.RARE, mage.cards.g.GoblinRazerunners.class)); + cards.add(new SetCardInfo("Goblin Ringleader", 48, Rarity.UNCOMMON, mage.cards.g.GoblinRingleader.class)); + cards.add(new SetCardInfo("Goblin Tunneler", 49, Rarity.COMMON, mage.cards.g.GoblinTunneler.class)); + cards.add(new SetCardInfo("Goblin Wardriver", 50, Rarity.UNCOMMON, mage.cards.g.GoblinWardriver.class)); + cards.add(new SetCardInfo("Harbinger of the Tides", 7, Rarity.RARE, mage.cards.h.HarbingerOfTheTides.class)); + cards.add(new SetCardInfo("Hordeling Outburst", 51, Rarity.UNCOMMON, mage.cards.h.HordelingOutburst.class)); + cards.add(new SetCardInfo("Inkfathom Divers", 8, Rarity.COMMON, mage.cards.i.InkfathomDivers.class)); + cards.add(new SetCardInfo("Island", 28, Rarity.LAND, mage.cards.basiclands.Island.class)); + cards.add(new SetCardInfo("Island", 29, Rarity.LAND, mage.cards.basiclands.Island.class)); + cards.add(new SetCardInfo("Island", 30, Rarity.LAND, mage.cards.basiclands.Island.class)); + cards.add(new SetCardInfo("Island", 31, Rarity.LAND, mage.cards.basiclands.Island.class)); + cards.add(new SetCardInfo("Krenko's Command", 53, Rarity.COMMON, mage.cards.k.KrenkosCommand.class)); + cards.add(new SetCardInfo("Krenko, Mob Boss", 52, Rarity.RARE, mage.cards.k.KrenkoMobBoss.class)); + cards.add(new SetCardInfo("Lonely Sandbar", 27, Rarity.COMMON, mage.cards.l.LonelySandbar.class)); + cards.add(new SetCardInfo("Master of Waves", 1, Rarity.MYTHIC, mage.cards.m.MasterOfWaves.class)); + cards.add(new SetCardInfo("Master of the Pearl Trident", 9, Rarity.RARE, mage.cards.m.MasterOfThePearlTrident.class)); + cards.add(new SetCardInfo("Merfolk Looter", 10, Rarity.UNCOMMON, mage.cards.m.MerfolkLooter.class)); + cards.add(new SetCardInfo("Merfolk Sovereign", 11, Rarity.RARE, mage.cards.m.MerfolkSovereign.class)); + cards.add(new SetCardInfo("Merfolk Wayfinder", 12, Rarity.UNCOMMON, mage.cards.m.MerfolkWayfinder.class)); + cards.add(new SetCardInfo("Merrow Reejerey", 13, Rarity.UNCOMMON, mage.cards.m.MerrowReejerey.class)); + cards.add(new SetCardInfo("Mind Spring", 14, Rarity.RARE, mage.cards.m.MindSpring.class)); + cards.add(new SetCardInfo("Misdirection", 15, Rarity.RARE, mage.cards.m.Misdirection.class)); + cards.add(new SetCardInfo("Mountain", 60, Rarity.LAND, mage.cards.basiclands.Mountain.class)); + cards.add(new SetCardInfo("Mountain", 61, Rarity.LAND, mage.cards.basiclands.Mountain.class)); + cards.add(new SetCardInfo("Mountain", 62, Rarity.LAND, mage.cards.basiclands.Mountain.class)); + cards.add(new SetCardInfo("Mountain", 63, Rarity.LAND, mage.cards.basiclands.Mountain.class)); + cards.add(new SetCardInfo("Relentless Assault", 54, Rarity.RARE, mage.cards.r.RelentlessAssault.class)); + cards.add(new SetCardInfo("Rootwater Hunter", 16, Rarity.UNCOMMON, mage.cards.r.RootwaterHunter.class)); + cards.add(new SetCardInfo("Scroll Thief", 17, Rarity.COMMON, mage.cards.s.ScrollThief.class)); + cards.add(new SetCardInfo("Streambed Aquitects", 18, Rarity.COMMON, mage.cards.s.StreambedAquitects.class)); + cards.add(new SetCardInfo("Tarfire", 55, Rarity.COMMON, mage.cards.t.Tarfire.class)); + cards.add(new SetCardInfo("Tidal Courier", 19, Rarity.UNCOMMON, mage.cards.t.TidalCourier.class)); + cards.add(new SetCardInfo("Tidal Warrior", 20, Rarity.COMMON, mage.cards.t.TidalWarrior.class)); + cards.add(new SetCardInfo("Tidal Wave", 21, Rarity.COMMON, mage.cards.t.TidalWave.class)); + cards.add(new SetCardInfo("Tidebinder Mage", 22, Rarity.RARE, mage.cards.t.TidebinderMage.class)); + cards.add(new SetCardInfo("Triton Tactics", 23, Rarity.UNCOMMON, mage.cards.t.TritonTactics.class)); + cards.add(new SetCardInfo("Wake Thrasher", 24, Rarity.RARE, mage.cards.w.WakeThrasher.class)); + cards.add(new SetCardInfo("Warren Instigator", 32, Rarity.MYTHIC, mage.cards.w.WarrenInstigator.class)); } } diff --git a/Mage.Sets/src/mage/sets/Planeshift.java b/Mage.Sets/src/mage/sets/Planeshift.java index bc0f02483c6..015f44c7f94 100644 --- a/Mage.Sets/src/mage/sets/Planeshift.java +++ b/Mage.Sets/src/mage/sets/Planeshift.java @@ -110,6 +110,7 @@ public class Planeshift extends ExpansionSet { cards.add(new SetCardInfo("Insolence", 63, Rarity.COMMON, mage.cards.i.Insolence.class)); cards.add(new SetCardInfo("Kavu Recluse", 64, Rarity.COMMON, mage.cards.k.KavuRecluse.class)); cards.add(new SetCardInfo("Keldon Mantle", 65, Rarity.COMMON, mage.cards.k.KeldonMantle.class)); + cards.add(new SetCardInfo("Keldon Twilight", 112, Rarity.RARE, mage.cards.k.KeldonTwilight.class)); cards.add(new SetCardInfo("Lashknife Barrier", 9, Rarity.UNCOMMON, mage.cards.l.LashknifeBarrier.class)); cards.add(new SetCardInfo("Lava Zombie", 113, Rarity.COMMON, mage.cards.l.LavaZombie.class)); cards.add(new SetCardInfo("Lord of the Undead", 44, Rarity.RARE, mage.cards.l.LordOfTheUndead.class)); diff --git a/Mage.Sets/src/mage/sets/PortalSecondAge.java b/Mage.Sets/src/mage/sets/PortalSecondAge.java index ced4eb2baff..f014ac1968b 100644 --- a/Mage.Sets/src/mage/sets/PortalSecondAge.java +++ b/Mage.Sets/src/mage/sets/PortalSecondAge.java @@ -168,6 +168,7 @@ public class PortalSecondAge extends ExpansionSet { cards.add(new SetCardInfo("Rally the Troops", 139, Rarity.UNCOMMON, mage.cards.r.RallyTheTroops.class)); cards.add(new SetCardInfo("Ravenous Rats", 27, Rarity.COMMON, mage.cards.r.RavenousRats.class)); cards.add(new SetCardInfo("Razorclaw Bear", 82, Rarity.RARE, mage.cards.r.RazorclawBear.class)); + cards.add(new SetCardInfo("Relentless Assault", 115, Rarity.RARE, mage.cards.r.RelentlessAssault.class)); cards.add(new SetCardInfo("Remove", 43, Rarity.UNCOMMON, mage.cards.r.Remove.class)); cards.add(new SetCardInfo("Renewing Touch", 83, Rarity.UNCOMMON, mage.cards.r.RenewingTouch.class)); cards.add(new SetCardInfo("Return of the Nightstalkers", 28, Rarity.RARE, mage.cards.r.ReturnOfTheNightstalkers.class)); diff --git a/Mage.Sets/src/mage/sets/Scourge.java b/Mage.Sets/src/mage/sets/Scourge.java index 122d20e4058..ad8585d5827 100644 --- a/Mage.Sets/src/mage/sets/Scourge.java +++ b/Mage.Sets/src/mage/sets/Scourge.java @@ -108,11 +108,14 @@ public class Scourge extends ExpansionSet { cards.add(new SetCardInfo("Enrage", 91, Rarity.UNCOMMON, mage.cards.e.Enrage.class)); cards.add(new SetCardInfo("Eternal Dragon", 12, Rarity.RARE, mage.cards.e.EternalDragon.class)); cards.add(new SetCardInfo("Extra Arms", 92, Rarity.UNCOMMON, mage.cards.e.ExtraArms.class)); + cards.add(new SetCardInfo("Faces of the Past", 35, Rarity.RARE, mage.cards.f.FacesOfThePast.class)); cards.add(new SetCardInfo("Fatal Mutation", 66, Rarity.UNCOMMON, mage.cards.f.FatalMutation.class)); cards.add(new SetCardInfo("Fierce Empath", 119, Rarity.COMMON, mage.cards.f.FierceEmpath.class)); cards.add(new SetCardInfo("Final Punishment", 67, Rarity.RARE, mage.cards.f.FinalPunishment.class)); cards.add(new SetCardInfo("Forgotten Ancient", 120, Rarity.RARE, mage.cards.f.ForgottenAncient.class)); cards.add(new SetCardInfo("Form of the Dragon", 93, Rarity.RARE, mage.cards.f.FormOfTheDragon.class)); + cards.add(new SetCardInfo("Force Bubble", 14, Rarity.RARE, mage.cards.f.ForceBubble.class)); + cards.add(new SetCardInfo("Frontline Strategist", 15, Rarity.COMMON, mage.cards.f.FrontlineStrategist.class)); cards.add(new SetCardInfo("Frozen Solid", 36, Rarity.COMMON, mage.cards.f.FrozenSolid.class)); cards.add(new SetCardInfo("Gilded Light", 16, Rarity.UNCOMMON, mage.cards.g.GildedLight.class)); cards.add(new SetCardInfo("Goblin Brigand", 94, Rarity.COMMON, mage.cards.g.GoblinBrigand.class)); @@ -128,6 +131,7 @@ public class Scourge extends ExpansionSet { cards.add(new SetCardInfo("Krosan Warchief", 123, Rarity.UNCOMMON, mage.cards.k.KrosanWarchief.class)); cards.add(new SetCardInfo("Kurgadon", 124, Rarity.UNCOMMON, mage.cards.k.Kurgadon.class)); cards.add(new SetCardInfo("Lethal Vapors", 68, Rarity.RARE, mage.cards.l.LethalVapors.class)); + cards.add(new SetCardInfo("Lingering Death", 69, Rarity.COMMON, mage.cards.l.LingeringDeath.class)); cards.add(new SetCardInfo("Long-Term Plans", 38, Rarity.UNCOMMON, mage.cards.l.LongTermPlans.class)); cards.add(new SetCardInfo("Mercurial Kite", 39, Rarity.COMMON, mage.cards.m.MercurialKite.class)); cards.add(new SetCardInfo("Metamorphose", 40, Rarity.UNCOMMON, mage.cards.m.Metamorphose.class)); diff --git a/Mage.Sets/src/mage/sets/TheDark.java b/Mage.Sets/src/mage/sets/TheDark.java index f5a5e276888..632dcbd46e9 100644 --- a/Mage.Sets/src/mage/sets/TheDark.java +++ b/Mage.Sets/src/mage/sets/TheDark.java @@ -134,6 +134,7 @@ public class TheDark extends ExpansionSet { cards.add(new SetCardInfo("Season of the Witch", 14, Rarity.RARE, mage.cards.s.SeasonOfTheWitch.class)); cards.add(new SetCardInfo("Sisters of the Flame", 73, Rarity.UNCOMMON, mage.cards.s.SistersOfTheFlame.class)); cards.add(new SetCardInfo("Skull of Orm", 106, Rarity.UNCOMMON, mage.cards.s.SkullOfOrm.class)); + cards.add(new SetCardInfo("Sorrow's Path", 116, Rarity.RARE, mage.cards.s.SorrowsPath.class)); cards.add(new SetCardInfo("Squire", 90, Rarity.COMMON, mage.cards.s.Squire.class)); cards.add(new SetCardInfo("Standing Stones", 107, Rarity.UNCOMMON, mage.cards.s.StandingStones.class)); cards.add(new SetCardInfo("Stone Calendar", 108, Rarity.RARE, mage.cards.s.StoneCalendar.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/mana/ConditionalManaTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/mana/ConditionalManaTest.java index 104cebecbf1..0a3daff161b 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/mana/ConditionalManaTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/mana/ConditionalManaTest.java @@ -30,6 +30,7 @@ package org.mage.test.cards.mana; import mage.abilities.keyword.FlyingAbility; import mage.constants.PhaseStep; import mage.constants.Zone; +import org.junit.Assert; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -75,9 +76,10 @@ public class ConditionalManaTest extends CardTestPlayerBase { @Test public void testWorkingWithReflectingPool() { - addCard(Zone.BATTLEFIELD, playerA, "Cavern of Souls", 1); - addCard(Zone.BATTLEFIELD, playerA, "Reflecting Pool", 1); // can create white mana without restriction from the Cavern - addCard(Zone.HAND, playerA, "Silvercoat Lion", 1); + addCard(Zone.BATTLEFIELD, playerA, "Cavern of Souls", 1); // can give {C] or {any} mana ({any} with restrictions) + addCard(Zone.BATTLEFIELD, playerA, "Reflecting Pool", 1); // must give {C} or {any} mana from the Cavern, but without restrictions + addCard(Zone.HAND, playerA, "Silvercoat Lion", 1); // white bear + addCard(Zone.BATTLEFIELD, playerA, "Upwelling", 1); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Silvercoat Lion"); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/mana/HarvesterDruidTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/mana/HarvesterDruidTest.java index f03c021f1ce..c8bcc3b548d 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/mana/HarvesterDruidTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/mana/HarvesterDruidTest.java @@ -33,10 +33,11 @@ import mage.constants.Zone; import org.junit.Assert; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; +import static org.mage.test.utils.ManaOptionsTestUtils.*; /** * - * @author LevelX2 + * @author LevelX2, JayDi85 */ public class HarvesterDruidTest extends CardTestPlayerBase { @@ -52,8 +53,10 @@ public class HarvesterDruidTest extends CardTestPlayerBase { execute(); ManaOptions options = playerA.getAvailableManaTest(currentGame); - Assert.assertEquals("Player should be able to create 2 red and 1 blue mana", "{U}{R}{R}", options.get(0).toString()); - Assert.assertEquals("Player should be able to create 1 red and 3 blue mana", "{U}{U}{R}", options.get(1).toString()); + assertDuplicatedManaOptions(options); + Assert.assertEquals(2, options.size()); + assertManaOptions("{U}{R}{R}", options); + assertManaOptions("{U}{U}{R}", options); } @Test @@ -68,9 +71,10 @@ public class HarvesterDruidTest extends CardTestPlayerBase { execute(); ManaOptions options = playerA.getAvailableManaTest(currentGame); - Assert.assertEquals("Player should be able to create 3 red and 1 blue mana", "{U}{R}{R}{R}", options.get(0).toString()); - Assert.assertEquals("Player should be able to create 2 red and 2 blue mana", "{U}{U}{R}{R}", options.get(1).toString()); - Assert.assertEquals("Player should be able to create 2 red and 2 blue mana", "{U}{U}{R}{R}", options.get(2).toString()); - Assert.assertEquals("Player should be able to create 1 red and 3 blue mana", "{U}{U}{U}{R}", options.get(3).toString()); + assertDuplicatedManaOptions(options); + Assert.assertEquals(3, options.size()); + assertManaOptions("{U}{R}{R}{R}", options); + assertManaOptions("{U}{U}{R}{R}", options); + assertManaOptions("{U}{U}{U}{R}", options); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/mana/NagaVitalistTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/mana/NagaVitalistTest.java index 9709cb03597..353b80bb1c9 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/mana/NagaVitalistTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/mana/NagaVitalistTest.java @@ -6,10 +6,11 @@ import mage.constants.Zone; import org.junit.Assert; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; +import static org.mage.test.utils.ManaOptionsTestUtils.manaOptionsContain; /** * - * @author escplan9 + * @author escplan9, JayDi85 */ public class NagaVitalistTest extends CardTestPlayerBase { @@ -21,21 +22,112 @@ public class NagaVitalistTest extends CardTestPlayerBase { private final String nagaVitalist = "Naga Vitalist"; /* - Reported bug (issue #3315) - Naga Vitalist could not produce any color mana with a Gift of Paradise enchanted on a forest. All lands on board were forests. - */ - @Test - public void nagaVitalist_InteractionGiftOfParadise() { - - /* Gift of Paradise 2G Enchantment - Aura Enchant - Land When Gift of Paradise enters the battlefield, you gain 3 life. Enchanted land has "T: Add two mana of any one color to your mana pool." - */ - String giftParadise = "Gift of Paradise"; + */ + private final String giftParadise = "Gift of Paradise"; + @Test + public void nagaVitalist_GiftOfParadiseCanAnyColor() { + // Mana pools don't empty as steps and phases end. + addCard(Zone.BATTLEFIELD, playerA, "Upwelling"); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + addCard(Zone.HAND, playerA, giftParadise); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); + + // manual mana cost, cause auto cost can get swamp to pay + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}"); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}"); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, giftParadise, "Swamp"); + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + Assert.assertTrue("playerA must cast {Any}{Any}", manaOptionsContain(playerA.getManaAvailable(currentGame), "{Any}{Any}")); + } + + public void nagaVitalist_GiftOfParadisesLandCanGiveAnyColorToNaga_Setup(int giftCastTurn, int nagaManaTapTurn, String nagaManaTapColor) { + // test errors on enchanted ability do not apply for "any mana search" on different steps + + addCard(Zone.BATTLEFIELD, playerA, "Upwelling"); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + addCard(Zone.HAND, playerA, giftParadise); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); + addCard(Zone.BATTLEFIELD, playerA, nagaVitalist, 1); + + // cast and enchant swamp land to any color + activateManaAbility(giftCastTurn, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}"); + activateManaAbility(giftCastTurn, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}"); + activateManaAbility(giftCastTurn, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}"); + castSpell(giftCastTurn, PhaseStep.PRECOMBAT_MAIN, playerA, giftParadise, "Swamp"); + + // activate red mana (by any from enchanted land) + activateManaAbility(nagaManaTapTurn, PhaseStep.POSTCOMBAT_MAIN, playerA, "{T}: Add to your mana pool one mana of any"); + setChoice(playerA, nagaManaTapColor); + + setStopAt(nagaManaTapTurn, PhaseStep.POSTCOMBAT_MAIN); + execute(); + } + + @Test + public void nagaVitalist_GiftOfParadisesLandCanGiveAnyColorToNaga_SameStep1() { + nagaVitalist_GiftOfParadisesLandCanGiveAnyColorToNaga_Setup(1, 1, "Red"); + + //logger.info(playerA.getManaPool().getMana().toString()); + //logger.info(playerA.getManaAvailable(currentGame).toString()); + //for(Permanent perm: currentGame.getBattlefield().getAllActivePermanents(playerA.getId())){ + // logger.info(perm.getIdName() + ": " + perm.getAbilities().toString()); + //} + assertTapped("Forest", true); + assertTapped(giftParadise, false); + assertTapped("Swamp", false); + assertTapped(nagaVitalist, true); + Assert.assertEquals(1, playerA.getManaPool().get(ManaType.RED)); + } + + @Test + public void nagaVitalist_GiftOfParadisesLandCanGiveAnyColorToNaga_DiffStep1() { + nagaVitalist_GiftOfParadisesLandCanGiveAnyColorToNaga_Setup(1, 2, "Red"); + + assertTapped("Forest", true); + assertTapped(giftParadise, false); + assertTapped("Swamp", false); + assertTapped(nagaVitalist, true); + Assert.assertEquals(1, playerA.getManaPool().get(ManaType.RED)); + } + + @Test + public void nagaVitalist_GiftOfParadisesLandCanGiveAnyColorToNaga_SameStep3() { + nagaVitalist_GiftOfParadisesLandCanGiveAnyColorToNaga_Setup(3, 3, "Red"); + + assertTapped("Forest", true); + assertTapped(giftParadise, false); + assertTapped("Swamp", false); + assertTapped(nagaVitalist, true); + Assert.assertEquals(1, playerA.getManaPool().get(ManaType.RED)); + } + + @Test + public void nagaVitalist_GiftOfParadisesLandCanGiveAnyColorToNaga_DiffStep2() { + nagaVitalist_GiftOfParadisesLandCanGiveAnyColorToNaga_Setup(3, 4, "Red"); + + assertTapped("Forest", true); + assertTapped(giftParadise, false); + assertTapped("Swamp", false); + assertTapped(nagaVitalist, true); + Assert.assertEquals(1, playerA.getManaPool().get(ManaType.RED)); + } + + /* + Reported bug (issue #3315) + Naga Vitalist could not produce any color mana with a Gift of Paradise enchanted on a forest. All lands on board were forests. + */ + @Test + public void nagaVitalist_InteractionGiftOfParadise() { addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); addCard(Zone.BATTLEFIELD, playerA, nagaVitalist); addCard(Zone.BATTLEFIELD, playerA, "Upwelling"); // mana pools do not empty at the end of phases or turns @@ -43,7 +135,7 @@ public class NagaVitalistTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, giftParadise, "Forest"); - activateManaAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add to your mana pool one mana of any type that a land you control could produce"); + activateManaAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add to your mana pool one mana of any"); setChoice(playerA, "Red"); setStopAt(3, PhaseStep.PRECOMBAT_MAIN); @@ -51,6 +143,8 @@ public class NagaVitalistTest extends CardTestPlayerBase { assertLife(playerA, 23); // gift of paradise ETB assertTapped(nagaVitalist, true); + assertTapped(giftParadise, false); + assertTapped("Forest", false); Assert.assertEquals("one red mana has to be in the mana pool", 1, playerA.getManaPool().get(ManaType.RED)); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/mana/ReflectingPoolTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/mana/ReflectingPoolTest.java index 8479228d8b7..9fb11214c70 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/mana/ReflectingPoolTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/mana/ReflectingPoolTest.java @@ -28,15 +28,18 @@ package org.mage.test.cards.mana; import mage.abilities.mana.ManaOptions; +import mage.constants.ManaType; import mage.constants.PhaseStep; import mage.constants.Zone; import org.junit.Assert; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; +import static org.mage.test.utils.ManaOptionsTestUtils.*; + /** * - * @author LevelX2 + * @author LevelX2, JayDi85 */ public class ReflectingPoolTest extends CardTestPlayerBase { @@ -173,16 +176,122 @@ public class ReflectingPoolTest extends CardTestPlayerBase { execute(); ManaOptions options = playerA.getAvailableManaTest(currentGame); - Assert.assertEquals("Player A should be able to create the ", "{G}{G}{G}", options.get(0).toString()); - Assert.assertEquals("Player A should be able to create the ", "{W}{G}{G}", options.get(1).toString()); - Assert.assertEquals("Player A should be able to create the ", "{W}{G}{G}", options.get(2).toString()); // ManaOption type optimzing seems not optimal yet - Assert.assertEquals("Player A should be able to create the ", "{W}{W}{G}", options.get(3).toString()); - Assert.assertEquals("Player A should be able to create only 3 different mana options", 4, options.size()); + Assert.assertEquals("Player A should be able to create only 3 different mana options", 3, options.size()); + assertManaOptions("{G}{G}{G}", options); + assertManaOptions("{W}{G}{G}", options); + assertManaOptions("{W}{W}{G}", options); options = playerB.getAvailableManaTest(currentGame); - Assert.assertEquals("Player B should be able to create the ", "{W}{G}", options.get(0).toString()); - Assert.assertEquals("Player B should be able to create the ", "{W}{W}", options.get(1).toString()); - Assert.assertEquals("Player B should be able to create only 3 different mana options", 2, options.size()); + Assert.assertEquals("Player B should be able to create only 2 different mana options", 2, options.size()); + assertManaOptions("{W}{G}", options); + assertManaOptions("{W}{W}", options); + } + + @Test + public void testReflectingPoolGiveNonMana() { + addCard(Zone.HAND, playerA, bear1, 1); + addCard(Zone.BATTLEFIELD, playerA, "Reflecting Pool", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bear1); // do not have any mana + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + Assert.assertEquals(0, playerA.getManaPool().getMana().count()); + assertPermanentCount(playerA, bear1, 0); + } + + @Test + public void testReflectingPoolGiveNonMana2() { + addCard(Zone.HAND, playerA, bear1, 1); + addCard(Zone.BATTLEFIELD, playerA, "Reflecting Pool", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bear1); // do not have any mana + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + Assert.assertEquals(0, playerA.getManaPool().getMana().count()); + assertPermanentCount(playerA, bear1, 0); + } + + @Test + public void testReflectingPoolGiveBasicManaNeed() { + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + addCard(Zone.HAND, playerA, bear1G, 1); + addCard(Zone.BATTLEFIELD, playerA, "Reflecting Pool", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bear1G); // have {G} mana to cast + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, bear1G, 1); + } + + @Test + public void testReflectingPoolGiveBasicManaNotNeed() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.HAND, playerA, bear1G, 1); + addCard(Zone.BATTLEFIELD, playerA, "Reflecting Pool", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bear1G); // have only {W} mana, can't cast + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, bear1G, 0); + } + + @Test + public void testReflectingPoolAnyManaNeedWithoutCondition() { + // any mana source without conditions (use any mana at any time) + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + addCard(Zone.BATTLEFIELD, playerA, "City of Brass", 1); + String bear2GG = "Razorclaw Bear"; + addCard(Zone.HAND, playerA, bear2GG, 1); + addCard(Zone.BATTLEFIELD, playerA, "Reflecting Pool", 1); + addCard(Zone.BATTLEFIELD, playerA, "Upwelling", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bear2GG); // 2 plains + 2 any -- can cast + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, bear2GG, 1); + } + + @Test + public void testReflectingPoolAnyManaNeedWithCondition() { + // any mana source have condition to use (Reflecting Pool must ignore that condition) + addCard(Zone.BATTLEFIELD, playerA, "Cavern of Souls", 1); // {C} or {any} + addCard(Zone.HAND, playerA, bear1G, 1); + addCard(Zone.BATTLEFIELD, playerA, "Reflecting Pool", 1); + addCard(Zone.BATTLEFIELD, playerA, "Upwelling", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bear1G); // {C} from cavern and {any} (green) from reflection + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, bear1G, 1); + } + + @Test + public void testReflectingPoolAnyManaTapped() { + // any mana source with tapped must allow use any too + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.BATTLEFIELD, playerA, "City of Brass", 1); + addCard(Zone.BATTLEFIELD, playerA, "Reflecting Pool", 1); + addCard(Zone.BATTLEFIELD, playerA, "Upwelling", 1); + + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add one mana of any"); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {W}"); + setChoice(playerA,"Black"); + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + logger.info(playerA.getManaPool().getMana().toString()); + logger.info(playerA.getManaAvailable(currentGame).toString()); + assertTapped("City of Brass", true); + assertTapped("Plains", true); + assertTapped("Reflecting Pool", false); + Assert.assertEquals(1, playerA.getManaPool().get(ManaType.BLACK)); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/mana/VorinclexVoiceOfHungerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/mana/VorinclexVoiceOfHungerTest.java index 8b78d5dccd7..b129aa19c79 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/mana/VorinclexVoiceOfHungerTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/mana/VorinclexVoiceOfHungerTest.java @@ -27,6 +27,7 @@ */ package org.mage.test.cards.mana; +import mage.constants.ManaType; import mage.constants.PhaseStep; import mage.constants.Zone; import mage.counters.CounterType; @@ -60,7 +61,29 @@ public class VorinclexVoiceOfHungerTest extends CardTestPlayerBase { execute(); assertPermanentCount(playerA, "Vedalken Mastermind", 1); + } + /** + * Vorinclex, Voice of Hunger is not mana doubling River of Tears. + */ + @Test + // @Ignore // TODO: need to fix Vorinclex, Voice of Hunger -- it's double fireup mana tap event + public void testVorinclexVoiceofHungerRiverOfTearsManaMultiplier() { + addCard(Zone.BATTLEFIELD, playerA, "Upwelling", 1); + addCard(Zone.HAND, playerA, "River of Tears", 1); + // Trample + // Whenever you tap a land for mana, add one mana to your mana pool of any type that land produced. + // Whenever an opponent taps a land for mana, that land doesn't untap during its controller's next untap step. + addCard(Zone.BATTLEFIELD, playerA, "Vorinclex, Voice of Hunger", 1); + // {T}: Add {U} to your mana pool. If you played a land this turn, add {B} to your mana pool instead. + playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "River of Tears"); + + activateManaAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {U} to your mana pool"); + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + assertManaPool(playerA, ManaType.BLUE, 2); } /** diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/PatronOfTheNezumiTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/PatronOfTheNezumiTest.java new file mode 100644 index 00000000000..67d0f76e215 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/PatronOfTheNezumiTest.java @@ -0,0 +1,82 @@ +package org.mage.test.cards.single; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JRHerlehy Created on 12/27/17. + */ +public class PatronOfTheNezumiTest extends CardTestPlayerBase { + + // Rat offering + // Whenever a permanent is put into an opponent's graveyard, that player loses 1 life. + + private final String patron = "Patron of the Nezumi"; + private final String thopter = "Ornithopter"; + private final String elspeth = "Elspeth, Sun's Champion"; + private final String elspethAbility = "+1: Create three"; + private final String elesh = "Elesh Norn, Grand Cenobite"; + private final String sinkhole = "Sinkhole"; + + @Test + public void testEffectWithTokens() { + this.setupTest(); + + this.addCard(Zone.BATTLEFIELD, playerB, elspeth); + + this.activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, elspethAbility); + this.activateAbility(4, PhaseStep.PRECOMBAT_MAIN, playerB, elspethAbility); + + this.castSpell(5, PhaseStep.PRECOMBAT_MAIN, playerA, elesh); + + this.setStopAt(5, PhaseStep.POSTCOMBAT_MAIN); + this.execute(); + + this.assertPermanentCount(playerB, 1); + this.assertLife(playerA, 20); + this.assertLife(playerB, 14); + } + + @Test + public void testEffectWithRIP() { + this.setupTest(); + + this.addCard(Zone.BATTLEFIELD, playerA, "Rest in Peace"); + this.addCard(Zone.BATTLEFIELD, playerB, thopter, 5); + + this.castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, elesh); + + this.setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + this.execute(); + + this.assertExileCount(playerB, 5); + this.assertLife(playerA, 20); + this.assertLife(playerB, 20); + } + + @Test + public void testEffectWithCreatures() { + this.setupTest(); + + this.addCard(Zone.BATTLEFIELD, playerB, thopter, 5); + + this.castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, elesh); + + this.setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + this.execute(); + + this.assertPermanentCount(playerB, 0); + this.assertLife(playerA, 20); + this.assertLife(playerB, 15); + } + + private void setupTest() { + this.addCard(Zone.BATTLEFIELD, playerA, patron); + this.addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5); + this.addCard(Zone.BATTLEFIELD, playerA, "Plains", 5); + this.addCard(Zone.HAND, playerA, sinkhole); + this.addCard(Zone.HAND, playerA, elesh); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/ZurTheEnchanterTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/ZurTheEnchanterTest.java index d5462cb059e..b291d73332e 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/ZurTheEnchanterTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/ZurTheEnchanterTest.java @@ -66,7 +66,7 @@ public class ZurTheEnchanterTest extends CardTestPlayerBase { addCard(Zone.HAND, playerB, "Diplomatic Immunity"); // {1}{U} // Enchant creature // Enchanted creature gets +1/+1 for each card in your hand. - addCard(Zone.LIBRARY, playerB, "Empyrial Armor"); + addCard(Zone.LIBRARY, playerB, "Empyrial Armor", 2); castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Diplomatic Immunity", "Zur the Enchanter"); diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java index fcf5b769b0e..537d8b18422 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java @@ -837,22 +837,22 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement ManaPool manaPool = currentGame.getPlayer(player.getId()).getManaPool(); switch (color){ case COLORLESS: - Assert.assertEquals(manaPool.getColorless() + manaPool.getConditionalMana().stream().mapToInt(Mana::getColorless).sum(), amount); + Assert.assertEquals(amount,manaPool.getColorless() + manaPool.getConditionalMana().stream().mapToInt(Mana::getColorless).sum()); break; case RED: - Assert.assertEquals(manaPool.getRed() + manaPool.getConditionalMana().stream().mapToInt(Mana::getRed).sum(), amount); + Assert.assertEquals(amount,manaPool.getRed() + manaPool.getConditionalMana().stream().mapToInt(Mana::getRed).sum()); break; case BLUE: - Assert.assertEquals(manaPool.getBlue() + manaPool.getConditionalMana().stream().mapToInt(Mana::getBlue).sum(), amount); + Assert.assertEquals(amount,manaPool.getBlue() + manaPool.getConditionalMana().stream().mapToInt(Mana::getBlue).sum()); break; case WHITE: - Assert.assertEquals(manaPool.getWhite() + manaPool.getConditionalMana().stream().mapToInt(Mana::getWhite).sum(), amount); + Assert.assertEquals(amount,manaPool.getWhite() + manaPool.getConditionalMana().stream().mapToInt(Mana::getWhite).sum()); break; case GREEN: - Assert.assertEquals(manaPool.getGreen() + manaPool.getConditionalMana().stream().mapToInt(Mana::getGreen).sum(), amount); + Assert.assertEquals(amount,manaPool.getGreen() + manaPool.getConditionalMana().stream().mapToInt(Mana::getGreen).sum()); break; case BLACK: - Assert.assertEquals(manaPool.getBlack() + manaPool.getConditionalMana().stream().mapToInt(Mana::getBlack).sum(), amount); + Assert.assertEquals(amount,manaPool.getBlack() + manaPool.getConditionalMana().stream().mapToInt(Mana::getBlack).sum()); break; } } diff --git a/Mage.Tests/src/test/java/org/mage/test/utils/ManaOptionsTest.java b/Mage.Tests/src/test/java/org/mage/test/utils/ManaOptionsTest.java index 91ea81bf863..7bb3f892bed 100644 --- a/Mage.Tests/src/test/java/org/mage/test/utils/ManaOptionsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/utils/ManaOptionsTest.java @@ -35,12 +35,13 @@ import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; +import static org.mage.test.utils.ManaOptionsTestUtils.*; /** * This test checks if the calculated possible mana options are correct related * to the given mana sources available. * - * @author LevelX2 + * @author LevelX2, JayDi85 */ public class ManaOptionsTest extends CardTestPlayerBase { @@ -52,9 +53,10 @@ public class ManaOptionsTest extends CardTestPlayerBase { execute(); ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + assertDuplicatedManaOptions(manaOptions); Assert.assertEquals("mana variations don't fit", 1, manaOptions.size()); - Assert.assertEquals("{G}{G}{G}", getManaOption(0, manaOptions)); + assertManaOptions("{G}{G}{G}", manaOptions); } @@ -69,12 +71,13 @@ public class ManaOptionsTest extends CardTestPlayerBase { execute(); ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + assertDuplicatedManaOptions(manaOptions); Assert.assertEquals("mana variations don't fit", 4, manaOptions.size()); - Assert.assertEquals("{G}{G}{G}", getManaOption(0, manaOptions)); - Assert.assertEquals("{W}{R}{G}{G}", getManaOption(1, manaOptions)); - Assert.assertEquals("{W}{W}{R}{R}{G}", getManaOption(2, manaOptions)); - Assert.assertEquals("{W}{W}{W}{R}{R}{R}", getManaOption(3, manaOptions)); + assertManaOptions("{G}{G}{G}", manaOptions); + assertManaOptions("{W}{R}{G}{G}", manaOptions); + assertManaOptions("{W}{W}{R}{R}{G}", manaOptions); + assertManaOptions("{W}{W}{W}{R}{R}{R}", manaOptions); } @@ -89,18 +92,19 @@ public class ManaOptionsTest extends CardTestPlayerBase { execute(); ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + assertDuplicatedManaOptions(manaOptions); Assert.assertEquals("mana variations don't fit", 10, manaOptions.size()); - Assert.assertEquals("{C}{C}{C}", getManaOption(0, manaOptions)); - Assert.assertEquals("{C}{C}{W}", getManaOption(1, manaOptions)); - Assert.assertEquals("{C}{C}{U}", getManaOption(2, manaOptions)); - Assert.assertEquals("{C}{W}{W}", getManaOption(3, manaOptions)); - Assert.assertEquals("{C}{W}{U}", getManaOption(4, manaOptions)); - Assert.assertEquals("{C}{U}{U}", getManaOption(5, manaOptions)); - Assert.assertEquals("{W}{W}{W}", getManaOption(6, manaOptions)); - Assert.assertEquals("{W}{W}{U}", getManaOption(7, manaOptions)); - Assert.assertEquals("{W}{U}{U}", getManaOption(8, manaOptions)); - Assert.assertEquals("{U}{U}{U}", getManaOption(9, manaOptions)); + assertManaOptions("{C}{C}{C}", manaOptions); + assertManaOptions("{C}{C}{W}", manaOptions); + assertManaOptions("{C}{C}{U}", manaOptions); + assertManaOptions("{C}{W}{W}", manaOptions); + assertManaOptions("{C}{W}{U}", manaOptions); + assertManaOptions("{C}{U}{U}", manaOptions); + assertManaOptions("{W}{W}{W}", manaOptions); + assertManaOptions("{W}{W}{U}", manaOptions); + assertManaOptions("{W}{U}{U}", manaOptions); + assertManaOptions("{U}{U}{U}", manaOptions); } // Chromatic Sphere @@ -114,9 +118,10 @@ public class ManaOptionsTest extends CardTestPlayerBase { execute(); ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + assertDuplicatedManaOptions(manaOptions); Assert.assertEquals("mana variations don't fit", 1, manaOptions.size()); - Assert.assertEquals("{Any}{Any}", getManaOption(0, manaOptions)); + assertManaOptions("{Any}{Any}", manaOptions); } // Orochi Leafcaller @@ -131,9 +136,10 @@ public class ManaOptionsTest extends CardTestPlayerBase { execute(); ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + assertDuplicatedManaOptions(manaOptions); Assert.assertEquals("mana variations don't fit", 1, manaOptions.size()); - Assert.assertEquals("{W}{W}{Any}{Any}", getManaOption(0, manaOptions)); + assertManaOptions("{W}{W}{Any}{Any}", manaOptions); } // Crystal Quarry @@ -149,9 +155,10 @@ public class ManaOptionsTest extends CardTestPlayerBase { execute(); ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + assertDuplicatedManaOptions(manaOptions); Assert.assertEquals("mana variations don't fit", 1, manaOptions.size()); - Assert.assertEquals("{C}{W}{W}{G}{G}", getManaOption(0, manaOptions)); + assertManaOptions("{C}{W}{W}{G}{G}", manaOptions); } // Crystal Quarry @@ -167,10 +174,11 @@ public class ManaOptionsTest extends CardTestPlayerBase { execute(); ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + assertDuplicatedManaOptions(manaOptions); Assert.assertEquals("mana variations don't fit", 2, manaOptions.size()); - Assert.assertEquals("{C}{W}{W}{G}{G}{G}", getManaOption(0, manaOptions)); - Assert.assertEquals("{W}{U}{B}{R}{G}", getManaOption(1, manaOptions)); + assertManaOptions("{C}{W}{W}{G}{G}{G}", manaOptions); + assertManaOptions("{W}{U}{B}{R}{G}", manaOptions); } // Nykthos, Shrine to Nyx @@ -186,28 +194,30 @@ public class ManaOptionsTest extends CardTestPlayerBase { execute(); ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + assertDuplicatedManaOptions(manaOptions); Assert.assertEquals("mana variations don't fit", 2, manaOptions.size()); - Assert.assertEquals("{C}{G}{G}{G}", getManaOption(0, manaOptions)); - Assert.assertEquals("{G}{G}{G}{G}{G}", getManaOption(1, manaOptions)); + assertManaOptions("{C}{G}{G}{G}", manaOptions); + assertManaOptions("{G}{G}{G}{G}{G}", manaOptions); } @Test public void testNykthos2() { addCard(Zone.BATTLEFIELD, playerA, "Sedge Scorpion", 4); addCard(Zone.BATTLEFIELD, playerA, "Akroan Crusader", 3); - addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); - addCard(Zone.BATTLEFIELD, playerA, "Nykthos, Shrine to Nyx", 1); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); // {G} + addCard(Zone.BATTLEFIELD, playerA, "Nykthos, Shrine to Nyx", 1); // {C} setStopAt(1, PhaseStep.UPKEEP); execute(); ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + assertDuplicatedManaOptions(manaOptions); Assert.assertEquals("mana variations don't fit", 3, manaOptions.size()); - Assert.assertEquals("{C}{G}{G}{G}", getManaOption(0, manaOptions)); - Assert.assertEquals("{G}{G}{G}{G}{G}", getManaOption(1, manaOptions)); - Assert.assertEquals("{R}{R}{R}{G}", getManaOption(2, manaOptions)); + assertManaOptions("{C}{G}{G}{G}", manaOptions); + assertManaOptions("{G}{G}{G}{G}{G}", manaOptions); + assertManaOptions("{R}{R}{R}{G}", manaOptions); } @Test @@ -220,13 +230,46 @@ public class ManaOptionsTest extends CardTestPlayerBase { execute(); ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + assertDuplicatedManaOptions(manaOptions); Assert.assertEquals("mana variations don't fit", 1, manaOptions.size()); - Assert.assertEquals("{C}{G}{Any}", getManaOption(0, manaOptions)); + assertManaOptions("{C}{G}{Any}", manaOptions); } @Test - public void testMix1() { + public void testDuplicatedDontHave1() { + addCard(Zone.BATTLEFIELD, playerA, "City of Brass", 2); // Any + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + + setStopAt(1, PhaseStep.UPKEEP); + execute(); + + ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + assertDuplicatedManaOptions(manaOptions); + } + + @Test + public void testDuplicatedDontHave3() { + addCard(Zone.BATTLEFIELD, playerA, "Grove of the Burnwillows", 2); // R or G + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + + setStopAt(1, PhaseStep.UPKEEP); + execute(); + + ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + assertDuplicatedManaOptions(manaOptions); + } + + @Test + public void testDuplicatedHave() { + // getManaAvailable return any combination of mana variants evailable to player + // if mana ability cost another mana then if replaced in mana cost + // example: + // 1x forest + // 1x Chromatic Star ({1}, {T}, Sacrifice Chromatic Star: Add one mana of any color to your mana pool.) + // give {G}{Any}, but after pay it transform to {Any} (1 green will be pay) + // That's why there are can be duplicated records in getManaAvailable + // {1}, {T}, Sacrifice Chromatic Star: Add one mana of any color to your mana pool. // When Chromatic Star is put into a graveyard from the battlefield, draw a card. addCard(Zone.BATTLEFIELD, playerA, "Chromatic Star", 1); @@ -242,10 +285,9 @@ public class ManaOptionsTest extends CardTestPlayerBase { execute(); ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); - - Assert.assertEquals("mana variations don't fit", 2, manaOptions.size()); - Assert.assertEquals("{Any}{Any}", getManaOption(0, manaOptions)); - Assert.assertEquals("{Any}{Any}", getManaOption(1, manaOptions)); + Assert.assertEquals("mana variations don't fit", 1, manaOptions.size()); + assertDuplicatedManaOptions(manaOptions); + assertManaOptions("{Any}{Any}", manaOptions); } @Test @@ -257,12 +299,13 @@ public class ManaOptionsTest extends CardTestPlayerBase { execute(); ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + assertDuplicatedManaOptions(manaOptions); Assert.assertEquals("mana variations don't fit", 4, manaOptions.size()); - Assert.assertEquals("{C}{W}", getManaOption(0, manaOptions)); - Assert.assertEquals("{W}{W}", getManaOption(1, manaOptions)); - Assert.assertEquals("{W}{B}", getManaOption(2, manaOptions)); - Assert.assertEquals("{B}{B}", getManaOption(3, manaOptions)); + assertManaOptions("{C}{W}", manaOptions); + assertManaOptions("{W}{W}", manaOptions); + assertManaOptions("{W}{B}", manaOptions); + assertManaOptions("{B}{B}", manaOptions); } /** @@ -278,9 +321,10 @@ public class ManaOptionsTest extends CardTestPlayerBase { execute(); ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + assertDuplicatedManaOptions(manaOptions); Assert.assertEquals("mana variations don't fit", 1, manaOptions.size()); - Assert.assertEquals("{W}{B}", getManaOption(0, manaOptions)); + assertManaOptions("{W}{B}", manaOptions); } @Test @@ -293,10 +337,11 @@ public class ManaOptionsTest extends CardTestPlayerBase { execute(); ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + assertDuplicatedManaOptions(manaOptions); Assert.assertEquals("mana variations don't fit", 2, manaOptions.size()); - Assert.assertEquals("{W}{B}{B}", getManaOption(0, manaOptions)); - Assert.assertEquals("{B}{B}{B}", getManaOption(1, manaOptions)); + assertManaOptions("{W}{B}{B}", manaOptions); + assertManaOptions("{B}{B}{B}", manaOptions); } @Test @@ -312,9 +357,10 @@ public class ManaOptionsTest extends CardTestPlayerBase { execute(); ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + assertDuplicatedManaOptions(manaOptions); Assert.assertEquals("mana variations don't fit", 1, manaOptions.size()); - Assert.assertEquals("{C}{W}{B}", getManaOption(0, manaOptions)); + assertManaOptions("{C}{W}{B}", manaOptions); } @Test @@ -331,9 +377,10 @@ public class ManaOptionsTest extends CardTestPlayerBase { execute(); ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + assertDuplicatedManaOptions(manaOptions); Assert.assertEquals("mana variations don't fit", 1, manaOptions.size()); - Assert.assertEquals("{C}{C}{C}{C}{W}{B}", getManaOption(0, manaOptions)); + assertManaOptions("{C}{C}{C}{C}{W}{B}", manaOptions); } @Test @@ -349,17 +396,9 @@ public class ManaOptionsTest extends CardTestPlayerBase { execute(); ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + assertDuplicatedManaOptions(manaOptions); Assert.assertEquals("mana variations don't fit", 1, manaOptions.size()); - Assert.assertEquals("{B}{B}", getManaOption(0, manaOptions)); - } - - // TODO - // Test Calciform Pools combination mana lands - private String getManaOption(int index, ManaOptions manaOptions) { - if (manaOptions.size() < index + 1) { - return ""; - } - return manaOptions.get(index).toString(); + assertManaOptions("{B}{B}", manaOptions); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/utils/ManaOptionsTestUtils.java b/Mage.Tests/src/test/java/org/mage/test/utils/ManaOptionsTestUtils.java new file mode 100644 index 00000000000..04d0e8cceac --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/utils/ManaOptionsTestUtils.java @@ -0,0 +1,48 @@ +package org.mage.test.utils; + +import mage.Mana; +import mage.abilities.mana.ManaOptions; +import org.junit.Assert; + +import java.util.HashSet; +import java.util.Set; + +public class ManaOptionsTestUtils { + + public static String bear1W = "Silvercoat Lion"; // {1}{W} + public static String bearG = "Basking Rootwalla"; // {G} + public static String bear1 = "Augmenting Automaton"; // {1} + public static String bear1G = "Balduvian Bears"; // {1}{G} + public static String bear2C = "Matter Reshaper"; // {2}{C} + + //mana info + //logger.info(playerA.getManaPool().getMana().toString()); + //logger.info(playerA.getManaAvailable(currentGame).toString()); + + public static boolean manaOptionsContain(ManaOptions list, String searchMana){ + for(Mana mana: list){ + if (mana.toString().equals(searchMana)){ + return true; + } + } + return false; + } + + public static void assertManaOptions(String searchMana, ManaOptions manaList){ + if(!manaOptionsContain(manaList, searchMana)){ + Assert.fail("Can't find " + searchMana + " in " + manaList.toString()); + } + } + + public static void assertDuplicatedManaOptions(ManaOptions manaList){ + Set list = new HashSet<>(); + for(Mana mana: manaList){ + String s = mana.toString(); + if(list.contains(s)){ + Assert.fail("Founded duplicated mana option " + s + " in " + manaList.toString()); + }else{ + list.add(s); + } + } + } +} diff --git a/Mage.Verify/src/main/java/mage/verify/JsonSet.java b/Mage.Verify/src/main/java/mage/verify/JsonSet.java index 4a56a5bd301..831721937fd 100644 --- a/Mage.Verify/src/main/java/mage/verify/JsonSet.java +++ b/Mage.Verify/src/main/java/mage/verify/JsonSet.java @@ -10,6 +10,7 @@ class JsonSet { public String gathererCode; public String magicCardsInfoCode; public String[] magicRaritiesCodes; + public String[] alternativeNames; public String releaseDate; public String border; public String type; diff --git a/Mage.Verify/src/main/java/mage/verify/MtgJson.java b/Mage.Verify/src/main/java/mage/verify/MtgJson.java index ea16d995383..79fd8722038 100644 --- a/Mage.Verify/src/main/java/mage/verify/MtgJson.java +++ b/Mage.Verify/src/main/java/mage/verify/MtgJson.java @@ -16,6 +16,36 @@ import java.util.Map; import java.util.zip.ZipInputStream; public final class MtgJson { + + public static Map mtgJsonToXMageCodes = new HashMap<>(); + public static Map xMageToMtgJsonCodes = new HashMap<>(); + + static { + mtgJsonToXMageCodes.put("pWCQ", "WMCQ"); + mtgJsonToXMageCodes.put("pSUS", "SUS"); + mtgJsonToXMageCodes.put("pPRE", "PTC"); + mtgJsonToXMageCodes.put("pMPR", "MPRP"); + mtgJsonToXMageCodes.put("pMEI", "MBP"); + mtgJsonToXMageCodes.put("pGTW", "GRC"); // pGTW - Gateway = GRC - WPN Gateway ??? + mtgJsonToXMageCodes.put("pGRU", "GUR"); + mtgJsonToXMageCodes.put("pGPX", "GPX"); + mtgJsonToXMageCodes.put("pFNM", "FNMP"); + mtgJsonToXMageCodes.put("pELP", "EURO"); + mtgJsonToXMageCodes.put("pARL", "ARENA"); + mtgJsonToXMageCodes.put("pALP", "APAC"); + mtgJsonToXMageCodes.put("PO2", "P02"); + mtgJsonToXMageCodes.put("DD3_JVC", "DD3JVC"); + mtgJsonToXMageCodes.put("DD3_GVL", "DDD"); + mtgJsonToXMageCodes.put("DD3_EVG", "DD3EVG"); + mtgJsonToXMageCodes.put("DD3_DVD", "DDC"); + mtgJsonToXMageCodes.put("NMS", "NEM"); + + // revert search + for(Map.Entry entry: mtgJsonToXMageCodes.entrySet()){ + xMageToMtgJsonCodes.put(entry.getValue(), entry.getKey()); + } + } + private MtgJson() {} private static final class CardHolder { diff --git a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java index 5835ef9b067..a915fb1f09d 100644 --- a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java +++ b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java @@ -152,10 +152,13 @@ public class VerifyCardDataTest { public void checkWrongCardClasses(){ Collection errorsList = new ArrayList<>(); Map classesIndex = new HashMap<>(); + int totalCards = 0; Collection sets = Sets.getInstance().values(); for (ExpansionSet set : sets) { for (ExpansionSet.SetCardInfo checkCard : set.getSetCardInfo()) { + totalCards = totalCards + 1; + String currentClass = checkCard.getCardClass().toString(); if (classesIndex.containsKey(checkCard.getName())) { String needClass = classesIndex.get(checkCard.getName()); @@ -175,11 +178,45 @@ public class VerifyCardDataTest { System.out.println(error); } + // unique cards stats + System.out.println("Total unique cards: " + classesIndex.size() + ", total non unique cards (reprints): " + totalCards); + if (errorsList.size() > 0){ Assert.fail("DB have wrong card classes, founded errors: " + errorsList.size()); } } + @Test + public void checkMissingSets(){ + + Collection errorsList = new ArrayList<>(); + + int totalMissingSets = 0; + int totalMissingCards = 0; + Collection sets = Sets.getInstance().values(); + for(Map.Entry refEntry: MtgJson.sets().entrySet()){ + JsonSet refSet = refEntry.getValue(); + + // replace codes for aliases + String searchSet = MtgJson.mtgJsonToXMageCodes.getOrDefault(refSet.code, refSet.code); + + ExpansionSet mageSet = Sets.findSet(searchSet); + if(mageSet == null){ + totalMissingSets = totalMissingSets + 1; + totalMissingCards = totalMissingCards + refSet.cards.size(); + errorsList.add("Warning: missing set " + refSet.code + " - " + refSet.name + " (cards: " + refSet.cards.size() + ")"); + } + } + if(errorsList.size() > 0){ + errorsList.add("Warning: total missing sets: " + totalMissingSets + ", with missing cards: " + totalMissingCards); + } + + // only warnings + for (String error: errorsList) { + System.out.println(error); + } + } + private static final Pattern SHORT_JAVA_STRING = Pattern.compile("(?<=\")[A-Z][a-z]+(?=\")"); private Set findSourceTokens(Class c) throws IOException { diff --git a/Mage/src/main/java/mage/Mana.java b/Mage/src/main/java/mage/Mana.java index 5e4ea50e9a0..b2461d2b367 100644 --- a/Mage/src/main/java/mage/Mana.java +++ b/Mage/src/main/java/mage/Mana.java @@ -221,6 +221,19 @@ public class Mana implements Comparable, Serializable, Copyable { return new Mana(0, 0, 0, 0, 0, 0, 0, notNegative(num, "Colorless")); } + /** + * Creates a {@link Mana} object with the passed in {@code num} of Any + * mana. {@code num} can not be a negative value. Negative values will be + * logged and set to 0. + * + * @param num value of Any mana to create. + * @return a {@link Mana} object with the passed in {@code num} of Any + * mana. + */ + public static Mana AnyMana(int num) { + return new Mana(0, 0, 0, 0, 0, 0, notNegative(num, "Any"), 0); + } + /** * Adds mana from the passed in {@link Mana} object to this object. * diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index 8e52732e5d8..e2ded9987d2 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -407,7 +407,7 @@ public abstract class AbilityImpl implements Ability { } if (!game.isSimulation()) { // inform about x costs now, so canceled announcements are not shown in the log - if (announceString != null) { + if ((announceString != null) && (!announceString.equals(""))) { game.informPlayers(announceString); } if (variableManaCost != null) { diff --git a/Mage/src/main/java/mage/abilities/common/DealsDamageToACreatureAllTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealsDamageToACreatureAllTriggeredAbility.java index ee6215d7396..87f28e4dfc5 100644 --- a/Mage/src/main/java/mage/abilities/common/DealsDamageToACreatureAllTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DealsDamageToACreatureAllTriggeredAbility.java @@ -19,14 +19,29 @@ import mage.target.targetpointer.FixedTarget; /** * - * @author Ludwig.Hirth + * @author LevelX2 */ public class DealsDamageToACreatureAllTriggeredAbility extends TriggeredAbilityImpl { private final boolean combatDamageOnly; private final FilterPermanent filterPermanent; private final SetTargetPointer setTargetPointer; - + + /** + * This ability works only for permanents doing damage. + * + * @param effect + * @param optional + * @param filterPermanent The filter that restricts which permanets have to + * trigger + * @param setTargetPointer The target to be set to target pointer of the + * effect.
+ * - PLAYER = player controlling the damage source.
+ * - PERMANENT = source permanent.
+ * - PERMANENT_TARGET = damaged creature. + * @param combatDamageOnly The flag to determine if only combat damage has + * to trigger + */ public DealsDamageToACreatureAllTriggeredAbility(Effect effect, boolean optional, FilterPermanent filterPermanent, SetTargetPointer setTargetPointer, boolean combatDamageOnly) { super(Zone.BATTLEFIELD, effect, optional); this.combatDamageOnly = combatDamageOnly; @@ -64,7 +79,13 @@ public class DealsDamageToACreatureAllTriggeredAbility extends TriggeredAbilityI effect.setTargetPointer(new FixedTarget(permanent.getControllerId())); break; case PERMANENT: - effect.setTargetPointer(new FixedTarget(permanent.getId(), permanent.getZoneChangeCounter(game))); + effect.setTargetPointer(new FixedTarget(permanent, game)); + break; + case PERMANENT_TARGET: + Permanent permanent_target = game.getPermanentOrLKIBattlefield(event.getTargetId()); + if (permanent_target != null) { + effect.setTargetPointer(new FixedTarget(permanent_target, game)); + } break; } @@ -77,7 +98,7 @@ public class DealsDamageToACreatureAllTriggeredAbility extends TriggeredAbilityI @Override public String getRule() { - return "Whenever " + filterPermanent.getMessage() + " deals " - + (combatDamageOnly ? "combat ":"") + "damage to a creature, " + super.getRule(); + return "Whenever " + filterPermanent.getMessage() + " deals " + + (combatDamageOnly ? "combat " : "") + "damage to a creature, " + super.getRule(); } -} \ No newline at end of file +} diff --git a/Mage/src/main/java/mage/abilities/decorator/ConditionalManaEffect.java b/Mage/src/main/java/mage/abilities/decorator/ConditionalManaEffect.java index 10857231df1..e297e429fa5 100644 --- a/Mage/src/main/java/mage/abilities/decorator/ConditionalManaEffect.java +++ b/Mage/src/main/java/mage/abilities/decorator/ConditionalManaEffect.java @@ -79,8 +79,10 @@ public class ConditionalManaEffect extends ManaEffect { otherwiseEffect.setTargetPointer(this.targetPointer); } Mana mana = getMana(game, source); - - if (mana != null && mana.getAny() > 0) { + if (mana == null) { + return false; + } + if (mana.getAny() > 0) { int amount = mana.getAny(); ChoiceColor choice = new ChoiceColor(true); @@ -92,13 +94,14 @@ public class ConditionalManaEffect extends ManaEffect { createdMana = choice.getMana(amount); } + if (createdMana == null) { + return false; + } mana = createdMana; - } - - if (mana != null) { + // because the mana type is now choosen, fire the event with the mana information checkToFirePossibleEvents(mana, game, source); - controller.getManaPool().addMana(mana, game, source); } + controller.getManaPool().addMana(mana, game, source); return true; } diff --git a/Mage/src/main/java/mage/abilities/effects/AuraReplacementEffect.java b/Mage/src/main/java/mage/abilities/effects/AuraReplacementEffect.java index 873e81e5a50..7037434d077 100644 --- a/Mage/src/main/java/mage/abilities/effects/AuraReplacementEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/AuraReplacementEffect.java @@ -98,7 +98,7 @@ public class AuraReplacementEffect extends ReplacementEffectImpl { // Aura enters the battlefield attached Object object = game.getState().getValue("attachTo:" + card.getId()); if (object != null) { - if (object instanceof PermanentCard) { + if (object instanceof Permanent) { // Aura is attached to a permanent on the battlefield return false; } diff --git a/Mage/src/main/java/mage/abilities/effects/common/AddManaOfAnyColorEffect.java b/Mage/src/main/java/mage/abilities/effects/common/AddManaOfAnyColorEffect.java index bd9b5aacee5..52e3a0fb3bf 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/AddManaOfAnyColorEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/AddManaOfAnyColorEffect.java @@ -69,11 +69,13 @@ public class AddManaOfAnyColorEffect extends BasicManaEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - ChoiceColor choice = new ChoiceColor(true); + String mes = String.format("Select color of %d mana to add it to your mana pool", this.amount); + ChoiceColor choice = new ChoiceColor(true, mes, game.getObject(source.getSourceId())); if (controller.choose(outcome, choice, game)) { if (choice.getColor() == null) { - return false; // it happens, don't know how + // on user's reconnect choice dialog close and return null even with required settings + return false; } Mana createdMana = choice.getMana(amount); if (createdMana != null) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/ChooseCreatureTypeEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ChooseCreatureTypeEffect.java index 3a9b80b9941..f707e908249 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ChooseCreatureTypeEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ChooseCreatureTypeEffect.java @@ -33,6 +33,7 @@ import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.choices.Choice; +import mage.choices.ChoiceCreatureType; import mage.choices.ChoiceImpl; import mage.constants.Outcome; import mage.constants.SubType; @@ -63,9 +64,7 @@ public class ChooseCreatureTypeEffect extends OneShotEffect { mageObject = game.getObject(source.getSourceId()); } if (controller != null && mageObject != null) { - Choice typeChoice = new ChoiceImpl(true); - typeChoice.setMessage("Choose creature type"); - typeChoice.setChoices(SubType.getCreatureTypes(false).stream().map(SubType::toString).collect(Collectors.toCollection(LinkedHashSet::new))); + Choice typeChoice = new ChoiceCreatureType(mageObject); while (!controller.choose(outcome, typeChoice, game)) { if (!controller.canRespond()) { return false; diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainProtectionFromColorTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainProtectionFromColorTargetEffect.java index 9458a18b49b..d96a4ed4639 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainProtectionFromColorTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainProtectionFromColorTargetEffect.java @@ -55,7 +55,7 @@ public class GainProtectionFromColorTargetEffect extends GainAbilityTargetEffect public GainProtectionFromColorTargetEffect(Duration duration, ObjectColor protectColor) { super(new ProtectionAbility(new FilterCard()), duration); - choice = new ChoiceColor(true); + choice = new ChoiceColor(true, "Choose a color to gain protection against it"); if (protectColor != null) { choice.setChoice(protectColor.toString()); } diff --git a/Mage/src/main/java/mage/abilities/mana/AnyColorLandsProduceManaAbility.java b/Mage/src/main/java/mage/abilities/mana/AnyColorLandsProduceManaAbility.java index 3a6b662e4cc..2447bd0c501 100644 --- a/Mage/src/main/java/mage/abilities/mana/AnyColorLandsProduceManaAbility.java +++ b/Mage/src/main/java/mage/abilities/mana/AnyColorLandsProduceManaAbility.java @@ -225,6 +225,9 @@ class AnyColorLandsProduceManaEffect extends ManaEffect { if (types.getColorless() > 0) { netManas.add(Mana.ColorlessMana(1)); } + if (types.getAny() > 0) { + netManas.add(Mana.AnyMana(1)); + } return netManas; } diff --git a/Mage/src/main/java/mage/abilities/mana/ManaOptions.java b/Mage/src/main/java/mage/abilities/mana/ManaOptions.java index f5402422dc5..dd90f3a57d0 100644 --- a/Mage/src/main/java/mage/abilities/mana/ManaOptions.java +++ b/Mage/src/main/java/mage/abilities/mana/ManaOptions.java @@ -28,7 +28,10 @@ package mage.abilities.mana; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; + import mage.Mana; import mage.game.Game; @@ -326,4 +329,19 @@ public class ManaOptions extends ArrayList { payCombinations.add(newMana); payCombinationsStrings.add(newMana.toString()); } + + + public void removeDuplicated(){ + Set list = new HashSet<>(); + + for(int i = this.size() - 1; i >= 0; i--){ + String s = this.get(i).toString(); + if (list.contains(s)){ + // remove duplicated + this.remove(i); + }else{ + list.add(s); + } + } + } } diff --git a/Mage/src/main/java/mage/choices/Choice.java b/Mage/src/main/java/mage/choices/Choice.java index 9a20c066bb4..dbb9051750b 100644 --- a/Mage/src/main/java/mage/choices/Choice.java +++ b/Mage/src/main/java/mage/choices/Choice.java @@ -37,19 +37,40 @@ import java.util.Set; */ public interface Choice { - boolean isChosen(); - boolean isRequired(); - void clearChoice(); String getMessage(); void setMessage(String message); - void setChoice(String choice); - void setChoiceByKey(String choiceKey); - Set getChoices(); - Map getKeyChoices(); - void setChoices(Set choices); - void setKeyChoices(Map choices); - String getChoice(); - String getChoiceKey(); - boolean isKeyChoice(); + + String getSubMessage(); + void setSubMessage(String subMessage); + + void clearChoice(); + boolean isChosen(); + boolean isRequired(); + Choice copy(); + + // string choice + void setChoices(Set choices); + Set getChoices(); + void setChoice(String choice); + String getChoice(); + + // key-value choice + boolean isKeyChoice(); + void setKeyChoices(Map choices); + Map getKeyChoices(); + void setChoiceByKey(String choiceKey); + String getChoiceKey(); + String getChoiceValue(); + + // search + boolean isSearchEnabled(); + void setSearchEnabled(boolean isEnabled); + void setSearchText(String searchText); + String getSearchText(); + + // sorting + boolean isSortEnabled(); + void setSortData(Map sortData); + Map getSortData(); } diff --git a/Mage/src/main/java/mage/choices/ChoiceColor.java b/Mage/src/main/java/mage/choices/ChoiceColor.java index 55531613e8d..2eedbdf0d46 100644 --- a/Mage/src/main/java/mage/choices/ChoiceColor.java +++ b/Mage/src/main/java/mage/choices/ChoiceColor.java @@ -27,24 +27,30 @@ */ package mage.choices; -import java.util.ArrayList; +import mage.MageObject; import mage.Mana; import mage.ObjectColor; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + /** * - * @author BetaSteward_at_googlemail.com + * @author BetaSteward_at_googlemail.com, JayDi85 */ public class ChoiceColor extends ChoiceImpl { - public static final ArrayList colorChoices = new ArrayList<>(); + private static final ArrayList colorChoices = getBaseColors(); - static { - colorChoices.add("Green"); - colorChoices.add("Blue"); - colorChoices.add("Black"); - colorChoices.add("Red"); - colorChoices.add("White"); + public static ArrayList getBaseColors(){ + ArrayList arr = new ArrayList<>(); + arr.add("Green"); + arr.add("Blue"); + arr.add("Black"); + arr.add("Red"); + arr.add("White"); + return arr; } public ChoiceColor() { @@ -52,9 +58,24 @@ public class ChoiceColor extends ChoiceImpl { } public ChoiceColor(boolean required) { + this(required, "Choose color"); + } + + public ChoiceColor(boolean required, String chooseMessage){ + this(required, chooseMessage, ""); + } + + public ChoiceColor(boolean required, String chooseMessage, MageObject source){ + this(required, chooseMessage, source.getIdName()); + } + + public ChoiceColor(boolean required, String chooseMessage, String chooseSubMessage){ super(required); + this.choices.addAll(colorChoices); - this.message = "Choose color"; + + this.setMessage(chooseMessage); + this.setSubMessage(chooseSubMessage); } public ChoiceColor(final ChoiceColor choice) { diff --git a/Mage/src/main/java/mage/choices/ChoiceCreatureType.java b/Mage/src/main/java/mage/choices/ChoiceCreatureType.java index 558a6ecb5c4..7ffbab88294 100644 --- a/Mage/src/main/java/mage/choices/ChoiceCreatureType.java +++ b/Mage/src/main/java/mage/choices/ChoiceCreatureType.java @@ -1,5 +1,6 @@ package mage.choices; +import mage.MageObject; import mage.constants.SubType; import java.util.LinkedHashSet; @@ -7,10 +8,28 @@ import java.util.stream.Collectors; public class ChoiceCreatureType extends ChoiceImpl { + private static String DEFAULT_MESSAGE = "Choose a creature type"; + public ChoiceCreatureType() { - super(true); + this(true, DEFAULT_MESSAGE, null); + } + + public ChoiceCreatureType(MageObject source) { + this(true, DEFAULT_MESSAGE, source); + } + + public ChoiceCreatureType(String chooseMessage, MageObject source) { + this(true, chooseMessage, source); + } + + public ChoiceCreatureType(boolean required, String chooseMessage, MageObject source){ + super(required); this.setChoices(SubType.getCreatureTypes(false).stream().map(SubType::toString).collect(Collectors.toCollection(LinkedHashSet::new))); - this.message = "Choose a creature type:"; + this.setMessage(chooseMessage); + if(source != null) { + this.setSubMessage(source.getIdName()); + } + this.setSearchEnabled(true); } public ChoiceCreatureType(final ChoiceCreatureType choice) { diff --git a/Mage/src/main/java/mage/choices/ChoiceImpl.java b/Mage/src/main/java/mage/choices/ChoiceImpl.java index bee47239a86..e87c171b490 100644 --- a/Mage/src/main/java/mage/choices/ChoiceImpl.java +++ b/Mage/src/main/java/mage/choices/ChoiceImpl.java @@ -40,13 +40,18 @@ import java.util.Set; */ public class ChoiceImpl implements Choice, Serializable { + // TODO: add sorting to items protected boolean chosen; protected final boolean required; protected String choice; protected String choiceKey; protected Set choices = new LinkedHashSet<>(); protected Map keyChoices = new LinkedHashMap<>(); + protected Map sortData = new LinkedHashMap<>(); protected String message; + protected String subMessage; + protected boolean searchEnabled = true; // enable for all windows by default + protected String searchText; public ChoiceImpl() { this(false); @@ -61,9 +66,13 @@ public class ChoiceImpl implements Choice, Serializable { this.chosen = choice.chosen; this.required = choice.required; this.message = choice.message; + this.subMessage = choice.subMessage; + this.searchEnabled = choice.searchEnabled; + this.searchText = choice.searchText; this.choices.addAll(choice.choices); this.choiceKey = choice.choiceKey; - this.keyChoices = choice.keyChoices; // list should never change for the same object so copy by reference + this.keyChoices = choice.keyChoices; // list should never change for the same object so copy by reference TODO: check errors with that, it that ok? Color list is static + this.sortData = choice.sortData; } @Override @@ -88,6 +97,12 @@ public class ChoiceImpl implements Choice, Serializable { this.message = message; } + @Override + public String getSubMessage(){ return subMessage; } + + @Override + public void setSubMessage(String subMessage){ this.subMessage = subMessage; } + @Override public Set getChoices() { return choices; @@ -136,12 +151,22 @@ public class ChoiceImpl implements Choice, Serializable { return choiceKey; } + @Override + public String getChoiceValue() { + if ((keyChoices != null) && (keyChoices.containsKey(choiceKey))){ + return keyChoices.get(choiceKey); + }else{ + return null; + } + } + @Override public void setChoiceByKey(String choiceKey) { String choiceToSet = keyChoices.get(choiceKey); if (choiceToSet != null) { this.choice = choiceToSet; this.choiceKey = choiceKey; + this.chosen = true; } } @@ -150,4 +175,39 @@ public class ChoiceImpl implements Choice, Serializable { return !keyChoices.isEmpty(); } -} + @Override + public boolean isSearchEnabled(){ + return this.searchEnabled; + }; + + @Override + public void setSearchEnabled(boolean isEnabled){ + this.searchEnabled = isEnabled; + }; + + @Override + public void setSearchText(String searchText){ + this.searchText = searchText; + }; + + @Override + public String getSearchText(){ + return this.searchText; + }; + + @Override + public boolean isSortEnabled(){ + return (this.sortData != null) && !this.sortData.isEmpty(); + }; + + @Override + public void setSortData(Map sortData){ + this.sortData = sortData; + }; + + @Override + public Map getSortData(){ + return this.sortData; + }; + +} \ No newline at end of file diff --git a/Mage/src/main/java/mage/constants/SetTargetPointer.java b/Mage/src/main/java/mage/constants/SetTargetPointer.java index 04c3afb9245..edb71ab241c 100644 --- a/Mage/src/main/java/mage/constants/SetTargetPointer.java +++ b/Mage/src/main/java/mage/constants/SetTargetPointer.java @@ -33,5 +33,11 @@ package mage.constants; */ public enum SetTargetPointer { - NONE, PLAYER, SPELL, CARD, PERMANENT, ATTACHED_TO_CONTROLLER + NONE, + PLAYER, + SPELL, + CARD, + PERMANENT, + PERMANENT_TARGET, + ATTACHED_TO_CONTROLLER } diff --git a/Mage/src/main/java/mage/filter/StaticFilters.java b/Mage/src/main/java/mage/filter/StaticFilters.java index 2de923335ec..0e7f18bc02e 100644 --- a/Mage/src/main/java/mage/filter/StaticFilters.java +++ b/Mage/src/main/java/mage/filter/StaticFilters.java @@ -50,8 +50,8 @@ public final class StaticFilters { public static final FilterPermanent FILTER_OPPONENTS_PERMANENT_ARTIFACT = new FilterPermanent("artifact an opponent controls"); public static final FilterPermanent FILTER_OPPONENTS_PERMANENT_ARTIFACT_OR_CREATURE = new FilterPermanent("artifact or creature an opponent controls"); - public static final FilterPermanent FILTER_CONTROLLED_CREATURE = new FilterControlledCreaturePermanent(); - public static final FilterPermanent FILTER_CONTROLLED_A_CREATURE = new FilterControlledCreaturePermanent("a creature you control"); + public static final FilterControlledCreaturePermanent FILTER_CONTROLLED_CREATURE = new FilterControlledCreaturePermanent(); + public static final FilterControlledCreaturePermanent FILTER_CONTROLLED_A_CREATURE = new FilterControlledCreaturePermanent("a creature you control"); public static final FilterControlledCreaturePermanent FILTER_CONTROLLED_ANOTHER_CREATURE = new FilterControlledCreaturePermanent("another creature"); public static final FilterControlledPermanent FILTER_CONTROLLED_PERMANENT_NON_LAND = new FilterControlledPermanent("nonland permanent"); public static final FilterLandPermanent FILTER_LAND = new FilterLandPermanent(); diff --git a/Mage/src/main/java/mage/game/combat/Combat.java b/Mage/src/main/java/mage/game/combat/Combat.java index d26bc9ec608..159f950b76e 100644 --- a/Mage/src/main/java/mage/game/combat/Combat.java +++ b/Mage/src/main/java/mage/game/combat/Combat.java @@ -1421,6 +1421,19 @@ public class Combat implements Serializable, Copyable { } } } + boolean canRemove = false; + for (CombatGroup group : getBlockingGroups()) { + if (group.blockers.contains(blockerId)) { + group.blockers.remove(blockerId); + group.attackerOrder.clear(); + } + if (group.blockers.isEmpty()) { + canRemove = true; + } + } + if (canRemove) { + blockingGroups.remove(blockerId); + } Permanent creature = game.getPermanent(blockerId); if (creature != null) { creature.setBlocking(0); diff --git a/Mage/src/main/java/mage/game/combat/CombatGroup.java b/Mage/src/main/java/mage/game/combat/CombatGroup.java index 7ad9391ceac..1717901b6e6 100644 --- a/Mage/src/main/java/mage/game/combat/CombatGroup.java +++ b/Mage/src/main/java/mage/game/combat/CombatGroup.java @@ -256,7 +256,7 @@ public class CombatGroup implements Serializable, Copyable { if (attacker.getAbilities().containsKey(DeathtouchAbility.getInstance().getId())) { lethalDamage = 1; } else { - lethalDamage = blocker.getToughness().getValue() - blocker.getDamage(); + lethalDamage = Math.max(blocker.getToughness().getValue() - blocker.getDamage(), 0); } if (lethalDamage >= damage) { blocker.markDamage(damage, attacker.getId(), game, true, true); @@ -311,7 +311,7 @@ public class CombatGroup implements Serializable, Copyable { if (attacker.getAbilities().containsKey(DeathtouchAbility.getInstance().getId())) { lethalDamage = 1; } else { - lethalDamage = blocker.getToughness().getValue() - blocker.getDamage(); + lethalDamage = Math.max(blocker.getToughness().getValue() - blocker.getDamage(), 0); } if (lethalDamage >= damage) { if (!oldRuleDamage) { @@ -483,7 +483,7 @@ public class CombatGroup implements Serializable, Copyable { if (blocker.getAbilities().containsKey(DeathtouchAbility.getInstance().getId())) { lethalDamage = 1; } else { - lethalDamage = attacker.getToughness().getValue() - attacker.getDamage(); + lethalDamage = Math.max(attacker.getToughness().getValue() - attacker.getDamage(), 0); } if (lethalDamage >= damage) { assigned.put(attackerId, damage); diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 64c9fde58e0..6233a4ed6f2 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -2439,6 +2439,10 @@ public abstract class PlayerImpl implements Player, Serializable { for (Abilities manaAbilities : sourceWithCosts) { available.addManaWithCost(manaAbilities, game); } + + // remove duplicated variants (see ManaOptionsTest for info - when thats rises) + available.removeDuplicated(); + return available; } diff --git a/Mage/src/main/java/mage/watchers/common/CardsCycledOrDiscardedThisTurnWatcher.java b/Mage/src/main/java/mage/watchers/common/CardsCycledOrDiscardedThisTurnWatcher.java index 3dc715174a3..021ed7bdedd 100644 --- a/Mage/src/main/java/mage/watchers/common/CardsCycledOrDiscardedThisTurnWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/CardsCycledOrDiscardedThisTurnWatcher.java @@ -28,9 +28,12 @@ package mage.watchers.common; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.UUID; +import mage.MageObjectReference; import mage.cards.Card; import mage.cards.Cards; import mage.cards.CardsImpl; @@ -47,6 +50,7 @@ import mage.watchers.Watcher; public class CardsCycledOrDiscardedThisTurnWatcher extends Watcher { private final Map cycledOrDiscardedCardsThisTurn = new HashMap<>(); + private final Map> numberOfCycledOrDiscardedCardsThisTurn = new HashMap<>(); public CardsCycledOrDiscardedThisTurnWatcher() { super(CardsCycledOrDiscardedThisTurnWatcher.class.getSimpleName(), WatcherScope.GAME); @@ -57,11 +61,17 @@ public class CardsCycledOrDiscardedThisTurnWatcher extends Watcher { for (Entry entry : watcher.cycledOrDiscardedCardsThisTurn.entrySet()) { cycledOrDiscardedCardsThisTurn.put(entry.getKey(), entry.getValue().copy()); } + for (Entry> entry : watcher.numberOfCycledOrDiscardedCardsThisTurn.entrySet()) { + Set cycledOrDiscardedCards = new HashSet<>(); + cycledOrDiscardedCards.addAll(entry.getValue()); + numberOfCycledOrDiscardedCardsThisTurn.put(entry.getKey(), cycledOrDiscardedCards); + } + numberOfCycledOrDiscardedCardsThisTurn.putAll(watcher.numberOfCycledOrDiscardedCardsThisTurn); } @Override public void watch(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.DISCARDED_CARD + if (event.getType() == GameEvent.EventType.DISCARDED_CARD || event.getType() == GameEvent.EventType.CYCLED_CARD && event.getPlayerId() != null) { Card card = game.getCard(event.getTargetId()); @@ -69,6 +79,12 @@ public class CardsCycledOrDiscardedThisTurnWatcher extends Watcher { Cards c = getCardsCycledOrDiscardedThisTurn(event.getPlayerId()); c.add(card); cycledOrDiscardedCardsThisTurn.put(event.getPlayerId(), c); + Set cycledOrDiscardedCards = numberOfCycledOrDiscardedCardsThisTurn.get(event.getPlayerId()); + if (cycledOrDiscardedCards == null) { + cycledOrDiscardedCards = new HashSet<>(); + numberOfCycledOrDiscardedCardsThisTurn.put(event.getPlayerId(), cycledOrDiscardedCards); + } + cycledOrDiscardedCards.add(new MageObjectReference(card, game)); } } } @@ -77,10 +93,18 @@ public class CardsCycledOrDiscardedThisTurnWatcher extends Watcher { return cycledOrDiscardedCardsThisTurn.getOrDefault(playerId, new CardsImpl()); } + public int getNumberOfCardsCycledOrDiscardedThisTurn(UUID playerId) { + if (numberOfCycledOrDiscardedCardsThisTurn.containsKey(playerId)) { + return numberOfCycledOrDiscardedCardsThisTurn.get(playerId).size(); + } + return 0; + } + @Override public void reset() { super.reset(); cycledOrDiscardedCardsThisTurn.clear(); + numberOfCycledOrDiscardedCardsThisTurn.clear(); } @Override diff --git a/Utils/known-sets.txt b/Utils/known-sets.txt index 15cf4414447..f16d61f439a 100644 --- a/Utils/known-sets.txt +++ b/Utils/known-sets.txt @@ -51,7 +51,7 @@ Duel Decks: Izzet vs. Golgari|IzzetVsGolgari| Duel Decks: Jace vs. Chandra|JaceVsChandra| Duel Decks: Jace vs. Vraska|JaceVsVraska| Duel Decks: Knights vs. Dragons|KnightsVsDragons| -Duel Decks: Merfolk vs Goblins|MerfolkVsGoblins| +Duel Decks: Merfolk vs. Goblins|MerfolkVsGoblins| Duel Decks: Mind vs. Might|MindVsMight| Duel Decks: Nissa vs. Ob Nixilis|NissaVsObNixilis| Duel Decks: Phyrexia vs. the Coalition|PhyrexiaVsTheCoalition| diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index c45de6289df..2013cd36113 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -32127,7 +32127,7 @@ Aven of Enduring Hope|Hour of Devastation|5|C|{4}{W}|Creature - Bird Cleric|3|3| Crested Sunmare|Hour of Devastation|6|M|{3}{W}{W}|Creature - Horse|5|5|Other Horses you control have indestructible.$At the beginning of each end step, if you gained life this turn, create a 5/5 white Horse creature token.| Dauntless Aven|Hour of Devastation|7|C|{2}{W}|Creature - Bird Warrior|2|1|Flying$Whenever Dauntless Aven attacks, untap target creature you control.| Desert's Hold|Hour of Devastation|8|U|{2}{W}|Enchantment - Aura|||Enchant creature$When Desert's Hold enters the battlefield, if you control a Desert or there is a Desert card in your graveyard, you gain 3 life.$Enchanted creature can't attack or block, and its activated abilities can't be activated.| -Disposal Mummy|Hour of Devastation|9|C|{2}{W}|Creature - Zombie Jackal|2|3|When Disposal Mummy enters the battlefield, exile target card from an opponent's graveyard.| +Disposal Mummy|Hour of Devastation|9|C|{2}{W}|Creature - Zombie Jackal|2|3|When Disposal Mummy enters the battlefield, exile target card from an opponent's graveyard.| Djeru, With Eyes Open|Hour of Devastation|10|R|{3}{W}{W}|Legendary Creature - Human Warrior|4|3|Vigilance$When Djeru, With Eyes Open enters the battlefield, you may search your library for a planeswalker card, reveal it, put it into your hand, then shuffle your library.$If a source would deal damage to a planeswalker you control, prevent 1 of that damage.| Djeru's Renunciation|Hour of Devastation|11|C|{1}{W}|Instant|||Tap up to two target creatures.$Cycling {W} ({W}, Discard this card: Draw a card.)| Dutiful Servants|Hour of Devastation|12|C|{3}{W}|Creature - Zombie|2|5|| @@ -32779,4 +32779,63 @@ Willing Test Subject|Unstable|126|C|{2}{G}|Creature- Spider Monkey Scientist|2|2 Garbage Elemental|Unstable|82|U|{4}{R}|Creature - Elemental|3|2|Battle cry (Whenever this creature attacks, each other attacking creature gets +1/+0 until end of turn.)$When Garbage Elemental enters the battlefield, roll two six-sided dice. Create a number of 1/1 red Goblin creature tokens equal to the difference between those results.| Oddly Uneven|Unstable|15|R|{3}{W}{W}|Sorcery|||Choose one --$* Destroy each creature with an odd number of words in its name. (Hyphenated words are one word.)$* Destroy each creature with an even number of words in its name.| capital offense|Unstable|52|C|{2}{B}{B}|Instant|||target creature gets -x/-x until end of turn, where x is the number of times a capital letter appears in its rules text. (ignore reminder text and flavor text.) -Very Cryptic Command|Unstable|49|R|{1}{U}{U}{U}|Instant|||Choose two - Return target permanent to its controller’s hand.; Draw two cards, then discard a card.; Change the target of target spell with a single target.; Turn over target nontoken creature.| +Very Cryptic Command|Unstable|49|R|{1}{U}{U}{U}|Instant|||Choose two - Return target permanent to its controller�s hand.; Draw two cards, then discard a card.; Change the target of target spell with a single target.; Turn over target nontoken creature.| +Master of Waves|Duel Decks: Merfolk vs. Goblins|1|M|{3}{U}|Creature - Merfolk Wizard|2|1|Protection from red$Elemental creatures you control get +1/+1.$When Master of Waves enters the battlefield, create a number of 1/0 blue Elemental creature tokens equal to your devotion to blue. (Each {U} in the mana costs of permanents you control counts toward your devotion to blue.)| +Aquitect's Will|Duel Decks: Merfolk vs. Goblins|2|C|{U}|Tribal Sorcery - Merfolk|||Put a flood counter on target land. That land is an Island in addition to its other types for as long as it has a flood counter on it. If you control a Merfolk, draw a card.| +Claustrophobia|Duel Decks: Merfolk vs. Goblins|3|C|{1}{U}{U}|Enchantment - Aura|||Enchant creature$When Claustrophobia enters the battlefield, tap enchanted creature.$Enchanted creature doesn't untap during its controller's untap step.| +Concentrate|Duel Decks: Merfolk vs. Goblins|4|U|{2}{U}{U}|Sorcery|||Draw three cards.| +Engulf the Shore|Duel Decks: Merfolk vs. Goblins|5|R|{3}{U}|Instant|||Return to their owners' hands all creatures with toughness less than or equal to the number of Islands you control.| +Essence Scatter|Duel Decks: Merfolk vs. Goblins|6|C|{1}{U}|Instant|||Counter target creature spell.| +Harbinger of the Tides|Duel Decks: Merfolk vs. Goblins|7|R|{U}{U}|Creature - Merfolk Wizard|2|2|You may cast Harbinger of the Tides as though it had flash if you pay {2} more to cast it. (You may cast it any time you could cast an instant.)$When Harbinger of the Tides enters the battlefield, you may return target tapped creature an opponent controls to its owner's hand.| +Inkfathom Divers|Duel Decks: Merfolk vs. Goblins|8|C|{3}{U}{U}|Creature - Merfolk Soldier|3|3|Islandwalk (This creature can't be blocked as long as defending player controls an Island.)$When Inkfathom Divers enters the battlefield, look at the top four cards of your library, then put them back in any order.| +Master of the Pearl Trident|Duel Decks: Merfolk vs. Goblins|9|R|{U}{U}|Creature - Merfolk|2|2|Other Merfolk creatures you control get +1/+1 and have islandwalk. (They can't be blocked as long as defending player controls an Island.)| +Merfolk Looter|Duel Decks: Merfolk vs. Goblins|10|U|{1}{U}|Creature - Merfolk Rogue|1|1|{tap}: Draw a card, then discard a card.| +Merfolk Sovereign|Duel Decks: Merfolk vs. Goblins|11|R|{1}{U}{U}|Creature - Merfolk|2|2|Other Merfolk creatures you control get +1/+1.${tap}: Target Merfolk creature can't be blocked this turn.| +Merfolk Wayfinder|Duel Decks: Merfolk vs. Goblins|12|U|{2}{U}|Creature - Merfolk Scout|1|2|Flying$When Merfolk Wayfinder enters the battlefield, reveal the top three cards of your library. Put all Island cards revealed this way into your hand and the rest on the bottom of your library in any order.| +Merrow Reejerey|Duel Decks: Merfolk vs. Goblins|13|U|{2}{U}|Creature - Merfolk Soldier|2|2|Other Merfolk creatures you control get +1/+1.$Whenever you cast a Merfolk spell, you may tap or untap target permanent.| +Mind Spring|Duel Decks: Merfolk vs. Goblins|14|R|{X}{U}{U}|Sorcery|||Draw X cards.| +Misdirection|Duel Decks: Merfolk vs. Goblins|15|R|{3}{U}{U}|Instant|||You may exile a blue card from your hand rather than pay Misdirection's mana cost.$Change the target of target spell with a single target.| +Rootwater Hunter|Duel Decks: Merfolk vs. Goblins|16|U|{2}{U}|Creature - Merfolk|1|1|{tap}: Rootwater Hunter deals 1 damage to target creature or player.| +Scroll Thief|Duel Decks: Merfolk vs. Goblins|17|C|{2}{U}|Creature - Merfolk Rogue|1|3|Whenever Scroll Thief deals combat damage to a player, draw a card.| +Streambed Aquitects|Duel Decks: Merfolk vs. Goblins|18|C|{1}{U}{U}|Creature - Merfolk Scout|2|3|{tap}: Target Merfolk creature gets +1/+1 and gains islandwalk until end of turn. (It can't be blocked as long as defending player controls an Island.)${tap}: Target land becomes an Island until end of turn.| +Tidal Courier|Duel Decks: Merfolk vs. Goblins|19|U|{3}{U}|Creature - Merfolk|1|2|When Tidal Courier enters the battlefield, reveal the top four cards of your library. Put all Merfolk cards revealed this way into your hand and the rest on the bottom of your library in any order.${3}{U}: Tidal Courier gains flying until end of turn.| +Tidal Warrior|Duel Decks: Merfolk vs. Goblins|20|C|{U}|Creature - Merfolk Warrior|1|1|{tap}: Target land becomes an Island until end of turn.| +Tidal Wave|Duel Decks: Merfolk vs. Goblins|21|C|{2}{U}|Instant|||Create a 5/5 blue Wall creature token with defender. Sacrifice it at the beginning of the next end step.| +Tidebinder Mage|Duel Decks: Merfolk vs. Goblins|22|R|{U}{U}|Creature - Merfolk Wizard|2|2|When Tidebinder Mage enters the battlefield, tap target red or green creature an opponent controls. That creature doesn't untap during its controller's untap step for as long as you control Tidebinder Mage.| +Triton Tactics|Duel Decks: Merfolk vs. Goblins|23|U|{U}|Instant|||Up to two target creatures each get +0/+3 until end of turn. Untap those creatures. At this turn's next end of combat, tap each creature that was blocked by one of those creatures this turn and it doesn't untap during its controller's next untap step.| +Wake Thrasher|Duel Decks: Merfolk vs. Goblins|24|R|{2}{U}|Creature - Merfolk Soldier|1|1|Whenever a permanent you control becomes untapped, Wake Thrasher gets +1/+1 until end of turn.| +Cold-Eyed Selkie|Duel Decks: Merfolk vs. Goblins|25|R|{1}{GU}{GU}|Creature - Merfolk Rogue|1|1|Islandwalk (This creature can't be blocked as long as defending player controls an Island.)$Whenever Cold-Eyed Selkie deals combat damage to a player, you may draw that many cards.| +Blighted Cataract|Duel Decks: Merfolk vs. Goblins|26|U||Land|||{tap}: Add {C} to your mana pool.${5}{U}, {tap}, Sacrifice Blighted Cataract: Draw two cards.| +Lonely Sandbar|Duel Decks: Merfolk vs. Goblins|27|C||Land|||Lonely Sandbar enters the battlefield tapped.${tap}: Add {U} to your mana pool.$Cycling {U} ({U}, Discard this card: Draw a card.)| +Island|Duel Decks: Merfolk vs. Goblins|28|L||Basic Land - Island|||| +Island|Duel Decks: Merfolk vs. Goblins|29|L||Basic Land - Island|||| +Warren Instigator|Duel Decks: Merfolk vs. Goblins|32|M|{R}{R}|Creature - Goblin Berserker|1|1|Double strike$Whenever Warren Instigator deals damage to an opponent, you may put a Goblin creature card from your hand onto the battlefield.| +Battle Squadron|Duel Decks: Merfolk vs. Goblins|33|U|{3}{R}{R}|Creature - Goblin|*|*|Flying$Battle Squadron's power and toughness are each equal to the number of creatures you control.| +Boggart Brute|Duel Decks: Merfolk vs. Goblins|34|C|{2}{R}|Creature - Goblin Warrior|3|2|Menace (This creature can't be blocked except by two or more creatures.)| +Brute Strength|Duel Decks: Merfolk vs. Goblins|35|C|{1}{R}|Instant|||Target creature gets +3/+1 and gains trample until end of turn.| +Cleaver Riot|Duel Decks: Merfolk vs. Goblins|36|U|{4}{R}|Sorcery|||Creatures you control gain double strike until end of turn. (They deal both first-strike and regular combat damage.)| +Ember Hauler|Duel Decks: Merfolk vs. Goblins|37|U|{R}{R}|Creature - Goblin|2|2|{1}, Sacrifice Ember Hauler: Ember Hauler deals 2 damage to target creature or player.| +Foundry Street Denizen|Duel Decks: Merfolk vs. Goblins|38|C|{R}|Creature - Goblin Warrior|1|1|Whenever another red creature enters the battlefield under your control, Foundry Street Denizen gets +1/+0 until end of turn.| +Gempalm Incinerator|Duel Decks: Merfolk vs. Goblins|39|U|{2}{R}|Creature - Goblin|2|1|Cycling {1}{R} ({1}{R}, Discard this card: Draw a card.)$When you cycle Gempalm Incinerator, you may have it deal X damage to target creature, where X is the number of Goblins on the battlefield.| +Ghostfire|Duel Decks: Merfolk vs. Goblins|40|C|{2}{R}|Instant|||Ghostfire is colorless.$Ghostfire deals 3 damage to target creature or player.| +Goblin Chieftain|Duel Decks: Merfolk vs. Goblins|41|R|{1}{R}{R}|Creature - Goblin|2|2|Haste (This creature can attack and {tap} as soon as it comes under your control.)$Other Goblin creatures you control get +1/+1 and have haste.| +Goblin Diplomats|Duel Decks: Merfolk vs. Goblins|42|R|{1}{R}|Creature - Goblin|2|1|{tap}: Each creature attacks this turn if able.| +Goblin Glory Chaser|Duel Decks: Merfolk vs. Goblins|43|U|{R}|Creature - Goblin Warrior|1|1|Renown 1 (When this creature deals combat damage to a player, if it isn't renowned, put a +1/+1 counter on it and it becomes renowned.)$As long as Goblin Glory Chaser is renowned, it has menace. (It can't be blocked except by two or more creatures.)| +Goblin Goon|Duel Decks: Merfolk vs. Goblins|44|R|{3}{R}|Creature - Goblin Mutant|6|6|Goblin Goon can't attack unless you control more creatures than defending player.$Goblin Goon can't block unless you control more creatures than attacking player.| +Goblin Grenade|Duel Decks: Merfolk vs. Goblins|45|U|{R}|Sorcery|||As an additional cost to cast Goblin Grenade, sacrifice a Goblin.$Goblin Grenade deals 5 damage to target creature or player.| +Goblin Rabblemaster|Duel Decks: Merfolk vs. Goblins|46|R|{2}{R}|Creature - Goblin Warrior|2|2|Other Goblin creatures you control attack each combat if able.$At the beginning of combat on your turn, create a 1/1 red Goblin creature token with haste.$Whenever Goblin Rabblemaster attacks, it gets +1/+0 until end of turn for each other attacking Goblin.| +Goblin Razerunners|Duel Decks: Merfolk vs. Goblins|47|R|{2}{R}{R}|Creature - Goblin Warrior|3|4|{1}{R}, Sacrifice a land: Put a +1/+1 counter on Goblin Razerunners.$At the beginning of your end step, you may have Goblin Razerunners deal damage equal to the number of +1/+1 counters on it to target player.| +Goblin Ringleader|Duel Decks: Merfolk vs. Goblins|48|U|{3}{R}|Creature - Goblin|2|2|Haste$When Goblin Ringleader enters the battlefield, reveal the top four cards of your library. Put all Goblin cards revealed this way into your hand and the rest on the bottom of your library in any order.| +Goblin Tunneler|Duel Decks: Merfolk vs. Goblins|49|C|{1}{R}|Creature - Goblin Rogue|1|1|{tap}: Target creature with power 2 or less can't be blocked this turn.| +Goblin Wardriver|Duel Decks: Merfolk vs. Goblins|50|U|{R}{R}|Creature - Goblin Warrior|2|2|Battle cry (Whenever this creature attacks, each other attacking creature gets +1/+0 until end of turn.)| +Hordeling Outburst|Duel Decks: Merfolk vs. Goblins|51|U|{1}{R}{R}|Sorcery|||Create three 1/1 red Goblin creature tokens.| +Krenko, Mob Boss|Duel Decks: Merfolk vs. Goblins|52|R|{2}{R}{R}|Legendary Creature - Goblin Warrior|3|3|{tap}: Create X 1/1 red Goblin creature tokens, where X is the number of Goblins you control.| +Krenko's Command|Duel Decks: Merfolk vs. Goblins|53|C|{1}{R}|Sorcery|||Create two 1/1 red Goblin creature tokens.| +Relentless Assault|Duel Decks: Merfolk vs. Goblins|54|R|{2}{R}{R}|Sorcery|||Untap all creatures that attacked this turn. After this main phase, there is an additional combat phase followed by an additional main phase.| +Tarfire|Duel Decks: Merfolk vs. Goblins|55|C|{R}|Tribal Instant - Goblin|||Tarfire deals 2 damage to target creature or player.| +Brittle Effigy|Duel Decks: Merfolk vs. Goblins|56|R|{1}|Artifact|||{4}, {tap}, Exile Brittle Effigy: Exile target creature.| +Goblin Charbelcher|Duel Decks: Merfolk vs. Goblins|57|R|{4}|Artifact|||{3}, {tap}: Reveal cards from the top of your library until you reveal a land card. Goblin Charbelcher deals damage equal to the number of nonland cards revealed this way to target creature or player. If the revealed land card was a Mountain, Goblin Charbelcher deals double that damage instead. Put the revealed cards on the bottom of your library in any order.| +Blighted Gorge|Duel Decks: Merfolk vs. Goblins|58|U||Land|||{tap}: Add {C} to your mana pool.${4}{R}, {tap}, Sacrifice Blighted Gorge: Blighted Gorge deals 2 damage to target creature or player.| +Forgotten Cave|Duel Decks: Merfolk vs. Goblins|59|C||Land|||Forgotten Cave enters the battlefield tapped.${tap}: Add {R} to your mana pool.$Cycling {R} ({R}, Discard this card: Draw a card.)| +Mountain|Duel Decks: Merfolk vs. Goblins|60|L||Basic Land - Mountain|||| +Mountain|Duel Decks: Merfolk vs. Goblins|61|L||Basic Land - Mountain|||| diff --git a/readme.md b/readme.md index 1fd21bee43d..86fa502a958 100644 --- a/readme.md +++ b/readme.md @@ -2,11 +2,15 @@ [![Join the chat at https://gitter.im/magefree/mage](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/magefree/mage?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://travis-ci.org/magefree/mage.svg?branch=master)](https://travis-ci.org/magefree/mage) -XMage allows you to play Magic against one or more online players or computer opponents. It includes full rules enforcement for over **14 000** unique cards (over 27 000 counting all cards from different editions). Starting with *Morningtide*, all regular sets have nearly all the cards implemented ([detailed overview](http://ct-magefree.rhcloud.com/stats)). +XMage allows you to play Magic against one or more online players or computer opponents. It includes full rules enforcement for over **16 700** unique cards (over 32 000 counting all cards from different editions). Starting with *Morningtide*, all regular sets have nearly all the cards implemented ([detailed overview](http://ct-magefree.rhcloud.com/stats)). There are public servers where you can play XMage against other players. You can also host your own server to play against the AI and/or your friends. -You can visit the XMage forum [here](http://www.slightlymagic.net/forum/viewforum.php?f=70). +XMage community: +* [Official XMage forum](http://www.slightlymagic.net/forum/viewforum.php?f=70); +* [Official XMage support and feature request on github](https://github.com/magefree/mage/issues); +* [Reddit XMage group](https://www.reddit.com/r/XMage/); +* [Reddit XMage discord channel](https://discord.gg/Pqf42yn). ## Features