From 32cdd3b1eacf0e0b9a7eaa2b874142b7414312b5 Mon Sep 17 00:00:00 2001 From: Jonathan Skeate Date: Sat, 30 Dec 2017 14:35:15 -0500 Subject: [PATCH 01/45] Add rarity filters to deck editor --- .../mage/client/deckeditor/CardSelector.form | 103 ++++++++++++ .../mage/client/deckeditor/CardSelector.java | 154 +++++++++++++++++- 2 files changed, 254 insertions(+), 3 deletions(-) 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 478bd33dc4a..d5ab60e950f 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.form +++ b/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.form @@ -500,6 +500,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 d889a61e120..80c5600598f 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java @@ -55,6 +55,7 @@ import mage.client.util.GUISizeHelper; import mage.client.util.gui.FastSearchUtil; import mage.client.util.sets.ConstructedFormats; import mage.constants.CardType; +import mage.constants.Rarity; import mage.filter.FilterCard; import mage.filter.predicate.Predicate; import mage.filter.predicate.Predicates; @@ -214,8 +215,8 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene this.cards.add(card); } filterCards(); - } - + } + public void loadCards(BigCard bigCard) { this.bigCard = bigCard; this.btnBooster.setVisible(true); @@ -225,7 +226,7 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene // cbExpansionSet.setModel(new DefaultComboBoxModel<>(ConstructedFormats.getTypes())); // Action event on Expansion set triggers loadCards method cbExpansionSet.setSelectedIndex(0); - } + } private FilterCard buildFilter() { FilterCard filter = new FilterCard(); @@ -328,6 +329,24 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene // criteria.types(CardType.TRIBAL); // criteria.types(CardType.CONSPIRACY); + if (this.tbCommon.isSelected()) { + criteria.rarities(Rarity.COMMON); + criteria.rarities(Rarity.LAND); + } + if (this.tbUncommon.isSelected()) { + criteria.rarities(Rarity.UNCOMMON); + } + if (this.tbRare.isSelected()) { + criteria.rarities(Rarity.RARE); + } + if (this.tbMythic.isSelected()) { + criteria.rarities(Rarity.MYTHIC); + } + if (this.tbSpecial.isSelected()) { + criteria.rarities(Rarity.SPECIAL); + criteria.rarities(Rarity.BONUS); + } + if (this.cbExpansionSet.isVisible()) { String expansionSelection = this.cbExpansionSet.getSelectedItem().toString(); if (!expansionSelection.equals("- All Sets")) { @@ -376,6 +395,19 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene filterCards(); } + private void filterCardsRarity(int modifiers, String actionCommand) { + // ALT or CTRL button was pushed + if ((modifiers & ActionEvent.ALT_MASK) == ActionEvent.ALT_MASK || (modifiers & ActionEvent.CTRL_MASK) == ActionEvent.CTRL_MASK) { + boolean invert = (modifiers & ActionEvent.ALT_MASK) == ActionEvent.ALT_MASK; + tbCommon.setSelected(inverter(invert, tbCommon.getActionCommand(), actionCommand)); + tbUncommon.setSelected(inverter(invert, tbUncommon.getActionCommand(), actionCommand)); + tbRare.setSelected(inverter(invert, tbRare.getActionCommand(), actionCommand)); + tbMythic.setSelected(inverter(invert, tbMythic.getActionCommand(), actionCommand)); + tbSpecial.setSelected(inverter(invert, tbSpecial.getActionCommand(), actionCommand)); + } + filterCards(); + } + private void filterCards() { FilterCard filter = buildFilter(); try { @@ -494,6 +526,13 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene jSeparator4 = new javax.swing.JToolBar.Separator(); jToggleListView = new javax.swing.JToggleButton(); jToggleCardView = new javax.swing.JToggleButton(); + jSeparator5 = new javax.swing.JToolBar.Separator(); + tbRarities = new javax.swing.JToolBar(); + tbCommon = new javax.swing.JToggleButton(); + tbUncommon = new javax.swing.JToggleButton(); + tbRare = new javax.swing.JToggleButton(); + tbMythic = new javax.swing.JToggleButton(); + tbSpecial = new javax.swing.JToggleButton(); cardSelectorScrollPane = new javax.swing.JScrollPane(); cardSelectorBottomPanel = new javax.swing.JPanel(); jButtonAddToMain = new javax.swing.JButton(); @@ -841,6 +880,88 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene } }); tbTypes.add(jToggleCardView); + tbTypes.add(jSeparator5); + + tbRarities.setFloatable(false); + tbRarities.setRollover(true); + tbRarities.setToolTipText("Hold the ALT-key while clicking to deselect all other card rarities or hold the CTRL-key to only select all other card rarities."); + + tbCommon.setIcon(new javax.swing.ImageIcon(getClass().getResource("/buttons/rarity_common_20.png"))); // NOI18N + tbCommon.setSelected(true); + tbCommon.setToolTipText("Common
" + + tbRarities.getToolTipText()); + tbCommon.setActionCommand("Common"); + tbCommon.setFocusable(false); + tbCommon.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + tbCommon.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); + tbCommon.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + tbCommonActionPerformed(evt); + } + }); + tbRarities.add(tbCommon); + + tbUncommon.setIcon(new javax.swing.ImageIcon(getClass().getResource("/buttons/rarity_uncommon_20.png"))); // NOI18N + tbUncommon.setSelected(true); + tbUncommon.setToolTipText("Uncommon
" + + tbUncommon.getToolTipText()); + tbUncommon.setActionCommand("Uncommon"); + tbUncommon.setFocusable(false); + tbUncommon.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + tbUncommon.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); + tbUncommon.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + tbUncommonActionPerformed(evt); + } + }); + tbRarities.add(tbUncommon); + + tbRare.setIcon(new javax.swing.ImageIcon(getClass().getResource("/buttons/rarity_rare_20.png"))); // NOI18N + tbRare.setSelected(true); + tbRare.setToolTipText("Rare
" + + tbRarities.getToolTipText()); + tbRare.setActionCommand("Rare"); + tbRare.setFocusable(false); + tbRare.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + tbRare.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); + tbRare.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + tbRareActionPerformed(evt); + } + }); + tbRarities.add(tbRare); + + tbMythic.setIcon(new javax.swing.ImageIcon(getClass().getResource("/buttons/rarity_mythic_20.png"))); // NOI18N + tbMythic.setSelected(true); + tbMythic.setToolTipText("Mythic
" + + tbRarities.getToolTipText()); + tbMythic.setActionCommand("Mythic"); + tbMythic.setFocusable(false); + tbMythic.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + tbMythic.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); + tbMythic.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + tbMythicActionPerformed(evt); + } + }); + tbRarities.add(tbMythic); + + tbSpecial.setIcon(new javax.swing.ImageIcon(getClass().getResource("/buttons/rarity_special_20.png"))); // NOI18N + tbSpecial.setSelected(true); + tbSpecial.setToolTipText("Special
" + + tbRarities.getToolTipText()); + tbSpecial.setActionCommand("Special"); + tbSpecial.setFocusable(false); + tbSpecial.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + tbSpecial.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); + tbSpecial.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + tbSpecialActionPerformed(evt); + } + }); + tbRarities.add(tbSpecial); + + tbTypes.add(tbRarities); cardSelectorScrollPane.setToolTipText("Double click to add the card to the main deck.
\nALT + Double click to add the card to the sideboard."); @@ -1224,6 +1345,26 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene FastSearchUtil.showFastSearchForStringComboBox(cbExpansionSet, "Select set or expansion"); }//GEN-LAST:event_btnExpansionSearchActionPerformed + private void tbCommonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_tbCommonActionPerformed + filterCardsRarity(evt.getModifiers(), evt.getActionCommand()); + }//GEN-LAST:event_tbCommonActionPerformed + + private void tbUncommonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_tbUncommonActionPerformed + filterCardsRarity(evt.getModifiers(), evt.getActionCommand()); + }//GEN-LAST:event_tbUncommonActionPerformed + + private void tbRareActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_tbRareActionPerformed + filterCardsRarity(evt.getModifiers(), evt.getActionCommand()); + }//GEN-LAST:event_tbRareActionPerformed + + private void tbMythicActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_tbMythicActionPerformed + filterCardsRarity(evt.getModifiers(), evt.getActionCommand()); + }//GEN-LAST:event_tbMythicActionPerformed + + private void tbSpecialActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_tbSpecialActionPerformed + filterCardsRarity(evt.getModifiers(), evt.getActionCommand()); + }//GEN-LAST:event_tbSpecialActionPerformed + private void toggleViewMode() { if (currentView instanceof CardGrid) { jToggleListView.setSelected(true); @@ -1288,6 +1429,7 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene private javax.swing.JToolBar.Separator jSeparator2; private javax.swing.JToolBar.Separator jSeparator3; private javax.swing.JToolBar.Separator jSeparator4; + private javax.swing.JToolBar.Separator jSeparator5; private javax.swing.JToolBar.Separator jSeparator6; private javax.swing.JTextField jTextFieldSearch; private javax.swing.JToggleButton jToggleCardView; @@ -1297,15 +1439,21 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene private javax.swing.JToggleButton tbBlue; private javax.swing.JToolBar tbColor; private javax.swing.JToggleButton tbColorless; + private javax.swing.JToggleButton tbCommon; private javax.swing.JToggleButton tbCreatures; private javax.swing.JToggleButton tbEnchantments; private javax.swing.JToggleButton tbGreen; private javax.swing.JToggleButton tbInstants; private javax.swing.JToggleButton tbLand; + private javax.swing.JToggleButton tbMythic; private javax.swing.JToggleButton tbPlaneswalkers; + private javax.swing.JToggleButton tbRare; + private javax.swing.JToolBar tbRarities; private javax.swing.JToggleButton tbRed; private javax.swing.JToggleButton tbSorceries; + private javax.swing.JToggleButton tbSpecial; private javax.swing.JToolBar tbTypes; + private javax.swing.JToggleButton tbUncommon; private javax.swing.JToggleButton tbWhite; // End of variables declaration//GEN-END:variables From 54c585afd8fa65a3e102d0f321d0d20ff976b8e6 Mon Sep 17 00:00:00 2001 From: igoudt Date: Sun, 31 Dec 2017 22:24:03 +0100 Subject: [PATCH 02/45] fixes #4317 --- .../predicate/permanent/ControllerIdPredicate.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Mage/src/main/java/mage/filter/predicate/permanent/ControllerIdPredicate.java b/Mage/src/main/java/mage/filter/predicate/permanent/ControllerIdPredicate.java index 1952990240d..7eeb8729bfd 100644 --- a/Mage/src/main/java/mage/filter/predicate/permanent/ControllerIdPredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/permanent/ControllerIdPredicate.java @@ -27,11 +27,12 @@ */ package mage.filter.predicate.permanent; -import java.util.UUID; import mage.filter.predicate.Predicate; import mage.game.Game; import mage.game.permanent.Permanent; +import java.util.UUID; + /** * * @author North @@ -46,6 +47,12 @@ public class ControllerIdPredicate implements Predicate { @Override public boolean apply(Permanent input, Game game) { + if(controllerId == null){ + return false; + } + if(input.getControllerId() == null){ + return false; + } return controllerId.equals(input.getControllerId()); } From 79b731a167aeecbfabdcc5a636cb01cd137dcc8c Mon Sep 17 00:00:00 2001 From: igoudt Date: Sun, 31 Dec 2017 22:42:41 +0100 Subject: [PATCH 03/45] fixes #4136 --- Mage.Sets/src/mage/cards/h/HeraldsHorn.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Mage.Sets/src/mage/cards/h/HeraldsHorn.java b/Mage.Sets/src/mage/cards/h/HeraldsHorn.java index f5e49c19bbb..f4524a00fa7 100644 --- a/Mage.Sets/src/mage/cards/h/HeraldsHorn.java +++ b/Mage.Sets/src/mage/cards/h/HeraldsHorn.java @@ -27,7 +27,6 @@ */ package mage.cards.h; -import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.AsEntersBattlefieldAbility; @@ -35,12 +34,8 @@ import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.ChooseCreatureTypeEffect; -import mage.abilities.effects.common.cost.SpellsCostReductionAllOfChosenSubtypeEffect; -import mage.cards.Card; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.cards.Cards; -import mage.cards.CardsImpl; +import mage.abilities.effects.common.cost.SpellsCostReductionControllerEffect; +import mage.cards.*; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.TargetController; @@ -50,6 +45,8 @@ import mage.filter.predicate.mageobject.ChosenSubtypePredicate; import mage.game.Game; import mage.players.Player; +import java.util.UUID; + /** * * @author Saga @@ -64,7 +61,7 @@ public class HeraldsHorn extends CardImpl { // Creature spells you cast of the chosen type cost {1} less to cast. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, - new SpellsCostReductionAllOfChosenSubtypeEffect(new FilterCreatureCard("Creature spells you cast of the chosen type"), 1))); + new SpellsCostReductionControllerEffect(new FilterCreatureCard("Creature spells you cast of the chosen type"), 1))); // At the beginning of your upkeep, look at the top card of your library. If it's a creature card of the chosen type, you may reveal it and put it into your hand. this.addAbility(new BeginningOfUpkeepTriggeredAbility(Zone.BATTLEFIELD, new HeraldsHornEffect(), TargetController.YOU, false)); From 60fabe1eba7f9f1d01684dfc47236f97fbc8b41d Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 31 Dec 2017 22:59:38 +0100 Subject: [PATCH 04/45] Implemented Feast of Flesh --- Mage.Sets/src/mage/cards/f/FeastOfFlesh.java | 77 ++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/f/FeastOfFlesh.java diff --git a/Mage.Sets/src/mage/cards/f/FeastOfFlesh.java b/Mage.Sets/src/mage/cards/f/FeastOfFlesh.java new file mode 100644 index 00000000000..3044b8b2070 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FeastOfFlesh.java @@ -0,0 +1,77 @@ +/* + * 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.dynamicvalue.IntPlusDynamicValue; +import mage.abilities.dynamicvalue.common.CardsInAllGraveyardsCount; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterCard; +import mage.filter.predicate.mageobject.NamePredicate; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author L_J + */ +public class FeastOfFlesh extends CardImpl { + + private static final FilterCard filter = new FilterCard("cards named Feast of Flesh"); + + static { + filter.add(new NamePredicate("Feast of Flesh")); + } + + public FeastOfFlesh(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{B}"); + + // Feast of Flesh deals X damage to target creature and you gain X life, where X is 1 plus the number of cards named Feast of Flesh in all graveyards. + IntPlusDynamicValue value = new IntPlusDynamicValue(1, new CardsInAllGraveyardsCount(filter)); + Effect effect1 = new DamageTargetEffect(value); + effect1.setText("Feast of Flesh deals X damage to target creature"); + Effect effect2 = new GainLifeEffect(value); + effect2.setText("and you gain X life, where X is 1 plus the number of cards named {source} in all graveyards"); + this.getSpellAbility().addEffect(effect1); + this.getSpellAbility().addEffect(effect2); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + public FeastOfFlesh(final FeastOfFlesh card) { + super(card); + } + + @Override + public FeastOfFlesh copy() { + return new FeastOfFlesh(this); + } +} From 9688c7c56b694cce0d87af3ecd0346c277ffda61 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 31 Dec 2017 23:00:22 +0100 Subject: [PATCH 05/45] Implemented Kjeldoran War Cry --- .../src/mage/cards/k/KjeldoranWarCry.java | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/k/KjeldoranWarCry.java diff --git a/Mage.Sets/src/mage/cards/k/KjeldoranWarCry.java b/Mage.Sets/src/mage/cards/k/KjeldoranWarCry.java new file mode 100644 index 00000000000..f7d5617f27a --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KjeldoranWarCry.java @@ -0,0 +1,73 @@ +/* + * 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.dynamicvalue.IntPlusDynamicValue; +import mage.abilities.dynamicvalue.common.CardsInAllGraveyardsCount; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.filter.FilterCard; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.NamePredicate; + +/** + * + * @author L_J + */ +public class KjeldoranWarCry extends CardImpl { + + private static final FilterCard filter = new FilterCard("cards named Kjeldoran War Cry"); + + static { + filter.add(new NamePredicate("Kjeldoran War Cry")); + } + + public KjeldoranWarCry(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{1}{W}"); + + // Creatures you control get +X/+X until end of turn, where X is 1 plus the number of cards named Kjeldoran War Cry in all graveyards. + IntPlusDynamicValue value = new IntPlusDynamicValue(1, new CardsInAllGraveyardsCount(filter)); + Effect effect = new BoostControlledEffect(value, value, Duration.EndOfTurn, new FilterCreaturePermanent("creatures"), false, true); + effect.setText("Creatures you control get +X/+X until end of turn, where X is 1 plus the number of cards named {source} in all graveyards"); + this.getSpellAbility().addEffect(effect); + } + + public KjeldoranWarCry(final KjeldoranWarCry card) { + super(card); + } + + @Override + public KjeldoranWarCry copy() { + return new KjeldoranWarCry(this); + } +} From cfd72b0c996314a3ac3bb0a457a8bd2be609c83d Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 31 Dec 2017 23:01:26 +0100 Subject: [PATCH 06/45] Implemented Thermal Flux --- Mage.Sets/src/mage/cards/t/ThermalFlux.java | 129 ++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/t/ThermalFlux.java diff --git a/Mage.Sets/src/mage/cards/t/ThermalFlux.java b/Mage.Sets/src/mage/cards/t/ThermalFlux.java new file mode 100644 index 00000000000..f4bcb7c8331 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/ThermalFlux.java @@ -0,0 +1,129 @@ +/* + * 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.t; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.common.delayed.AtTheBeginOfNextUpkeepDelayedTriggeredAbility; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.constants.SuperType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Layer; +import mage.constants.Outcome; +import mage.constants.SubLayer; +import mage.filter.FilterPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.SupertypePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; + +/** + * + * @author TheElk801 & L_J + */ +public class ThermalFlux extends CardImpl { + + private static final FilterPermanent filterNonsnow = new FilterPermanent("nonsnow permanent"); + private static final FilterPermanent filterSnow = new FilterPermanent("snow permanent"); + + static { + filterNonsnow.add(Predicates.not(new SupertypePredicate(SuperType.SNOW))); + filterSnow.add(new SupertypePredicate(SuperType.SNOW)); + } + + public ThermalFlux(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}"); + + // Choose one - + // Target nonsnow permanent becomes snow until end of turn. + // Draw a card at the beginning of the next turn's upkeep. + this.getSpellAbility().addTarget(new TargetPermanent(filterNonsnow)); + this.getSpellAbility().addEffect(new ThermalFluxEffect(true)); + this.getSpellAbility().addEffect(new CreateDelayedTriggeredAbilityEffect( + new AtTheBeginOfNextUpkeepDelayedTriggeredAbility(new DrawCardSourceControllerEffect(1)), false)); + // Target snow permanent isn't snow until end of turn. + // Draw a card at the beginning of the next turn's upkeep. + Mode mode = new Mode(); + mode.getTargets().add(new TargetPermanent(filterSnow)); + mode.getEffects().add(new ThermalFluxEffect(false)); + mode.getEffects().add(new CreateDelayedTriggeredAbilityEffect( + new AtTheBeginOfNextUpkeepDelayedTriggeredAbility(new DrawCardSourceControllerEffect(1)), false)); + this.getSpellAbility().addMode(mode); + + } + + public ThermalFlux(final ThermalFlux card) { + super(card); + } + + @Override + public ThermalFlux copy() { + return new ThermalFlux(this); + } +} + +class ThermalFluxEffect extends ContinuousEffectImpl { + + private final boolean addSnow; + + public ThermalFluxEffect(boolean addSnow) { + super(Duration.EndOfTurn, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Detriment); + this.addSnow = addSnow; + this.staticText = "Target " + (addSnow ? "non" : "") + "snow permanent " + (addSnow ? "becomes" : "isn't") + " snow until end of turn"; + } + + public ThermalFluxEffect(final ThermalFluxEffect effect) { + super(effect); + this.addSnow = effect.addSnow; + } + + @Override + public ThermalFluxEffect copy() { + return new ThermalFluxEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(source.getFirstTarget()); + if (permanent != null) { + if (addSnow) { + permanent.addSuperType(SuperType.SNOW); + } else { + permanent.getSuperType().remove(SuperType.SNOW); + } + } + return true; + } +} From ac865ce894ece09335aa1bd0017a56e1d0d440bc Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 31 Dec 2017 23:02:06 +0100 Subject: [PATCH 07/45] Implemented Void Maw --- Mage.Sets/src/mage/cards/v/VoidMaw.java | 190 ++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/v/VoidMaw.java diff --git a/Mage.Sets/src/mage/cards/v/VoidMaw.java b/Mage.Sets/src/mage/cards/v/VoidMaw.java new file mode 100644 index 00000000000..a2e4aa88922 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VoidMaw.java @@ -0,0 +1,190 @@ +/* + * 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.v; + +import java.util.UUID; +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.CostImpl; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCardInExile; +import mage.util.CardUtil; + +/** + * @author jeffwadsworth & L_J + */ +public class VoidMaw extends CardImpl { + + public VoidMaw(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{B}{B}"); + this.subtype.add(SubType.HORROR); + this.power = new MageInt(4); + this.toughness = new MageInt(5); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // If another creature would die, exile it instead. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new VoidMawEffect())); + + // Put a card exiled with Void Maw into its owner's graveyard: Void Maw gets +2/+2 until end of turn. + this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new BoostSourceEffect(2, 2, Duration.EndOfTurn), new VoidMawCost())); + } + + public VoidMaw(final VoidMaw card) { + super(card); + } + + @Override + public VoidMaw copy() { + return new VoidMaw(this); + } +} + +class VoidMawEffect extends ReplacementEffectImpl { + + public VoidMawEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "If another creature would die, exile it instead"; + } + + public VoidMawEffect(final VoidMawEffect effect) { + super(effect); + } + + @Override + public VoidMawEffect copy() { + return new VoidMawEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Player controller = game.getPlayer(source.getControllerId()); + MageObject sourceObject = source.getSourceObject(game); + if (controller != null && sourceObject != null) { + if (((ZoneChangeEvent) event).getFromZone() == Zone.BATTLEFIELD) { + Permanent permanent = ((ZoneChangeEvent) event).getTarget(); + if (permanent != null) { + UUID exileZoneId = CardUtil.getCardExileZoneId(game, source); + if (controller.moveCardsToExile(permanent, source, game, false, exileZoneId, sourceObject.getIdName())) { + return true; + } + } + } + } + return false; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ZONE_CHANGE; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + ZoneChangeEvent zEvent = (ZoneChangeEvent) event; + if (zEvent.getToZone() == Zone.GRAVEYARD) { + Permanent permanent = ((ZoneChangeEvent) event).getTarget(); + if (permanent != null && permanent.getId() != source.getSourceId()) { + if (zEvent.getTarget() != null) { // if it comes from permanent, check if it was a creature on the battlefield + if (zEvent.getTarget().isCreature()) { + return true; + } + } else if (permanent.isCreature()) { + return true; + } + } + } + return false; + } + +} + +class VoidMawCost extends CostImpl { + + public VoidMawCost() { + this.text = "Put a card exiled with {this} into its owner's graveyard"; + } + + public VoidMawCost(VoidMawCost cost) { + super(cost); + } + + @Override + public boolean pay(Ability ability, Game game, UUID sourceId, UUID controllerId, boolean noMana, Cost costToPay) { + Player controller = game.getPlayer(controllerId); + if (controller != null) { + TargetCardInExile target = new TargetCardInExile(new FilterCard(), CardUtil.getCardExileZoneId(game, ability)); + target.setNotTarget(true); + Cards cards = game.getExile().getExileZone(CardUtil.getCardExileZoneId(game, ability)); + if (!cards.isEmpty() + && controller.choose(Outcome.Benefit, cards, target, game)) { + Card card = game.getCard(target.getFirstTarget()); + if (card != null) { + if (controller.moveCardToGraveyardWithInfo(card, sourceId, game, Zone.EXILED)) { + paid = true; + } + } + } + } + return paid; + } + + @Override + public boolean canPay(Ability ability, UUID sourceId, UUID controllerId, Game game) { + Player player = game.getPlayer(controllerId); + return player != null; + } + + @Override + public VoidMawCost copy() { + return new VoidMawCost(this); + } +} From f2eaa5cf04d6cebf92b0f4404c7dd8836167f8ea Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 31 Dec 2017 23:04:26 +0100 Subject: [PATCH 08/45] Implemented cards --- Mage.Sets/src/mage/sets/Coldsnap.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Mage.Sets/src/mage/sets/Coldsnap.java b/Mage.Sets/src/mage/sets/Coldsnap.java index 1709fb4e29e..fd5daa98b1e 100644 --- a/Mage.Sets/src/mage/sets/Coldsnap.java +++ b/Mage.Sets/src/mage/sets/Coldsnap.java @@ -61,6 +61,7 @@ public class Coldsnap extends ExpansionSet { cards.add(new SetCardInfo("Arctic Nishoba", 102, Rarity.UNCOMMON, mage.cards.a.ArcticNishoba.class)); cards.add(new SetCardInfo("Arcum Dagsson", 27, Rarity.RARE, mage.cards.a.ArcumDagsson.class)); cards.add(new SetCardInfo("Aurochs Herd", 103, Rarity.COMMON, mage.cards.a.AurochsHerd.class)); + cards.add(new SetCardInfo("Balduvian Frostwaker", 28, Rarity.UNCOMMON, mage.cards.b.BalduvianFrostwaker.class)); cards.add(new SetCardInfo("Balduvian Rage", 76, Rarity.UNCOMMON, mage.cards.b.BalduvianRage.class)); cards.add(new SetCardInfo("Balduvian Warlord", 77, Rarity.UNCOMMON, mage.cards.b.BalduvianWarlord.class)); cards.add(new SetCardInfo("Blizzard Specter", 126, Rarity.UNCOMMON, mage.cards.b.BlizzardSpecter.class)); @@ -86,16 +87,20 @@ public class Coldsnap extends ExpansionSet { cards.add(new SetCardInfo("Disciple of Tevesh Szat", 55, Rarity.COMMON, mage.cards.d.DiscipleOfTeveshSzat.class)); cards.add(new SetCardInfo("Drelnoch", 32, Rarity.COMMON, mage.cards.d.Drelnoch.class)); cards.add(new SetCardInfo("Earthen Goo", 80, Rarity.UNCOMMON, mage.cards.e.EarthenGoo.class)); + cards.add(new SetCardInfo("Feast of Flesh", 56, Rarity.COMMON, mage.cards.f.FeastOfFlesh.class)); cards.add(new SetCardInfo("Field Marshal", 5, Rarity.RARE, mage.cards.f.FieldMarshal.class)); cards.add(new SetCardInfo("Flashfreeze", 33, Rarity.UNCOMMON, mage.cards.f.Flashfreeze.class)); cards.add(new SetCardInfo("Freyalise's Radiance", 108, Rarity.UNCOMMON, mage.cards.f.FreyalisesRadiance.class)); cards.add(new SetCardInfo("Frost Marsh", 146, Rarity.UNCOMMON, mage.cards.f.FrostMarsh.class)); cards.add(new SetCardInfo("Frost Raptor", 34, Rarity.COMMON, mage.cards.f.FrostRaptor.class)); + cards.add(new SetCardInfo("Frostweb Spider", 109, Rarity.COMMON, mage.cards.f.FrostwebSpider.class)); cards.add(new SetCardInfo("Frozen Solid", 35, Rarity.COMMON, mage.cards.f.FrozenSolid.class)); cards.add(new SetCardInfo("Fury of the Horde", 81, Rarity.RARE, mage.cards.f.FuryOfTheHorde.class)); cards.add(new SetCardInfo("Garza Zol, Plague Queen", 129, Rarity.RARE, mage.cards.g.GarzaZolPlagueQueen.class)); + cards.add(new SetCardInfo("Garza's Assassin", 57, Rarity.RARE, mage.cards.g.GarzasAssassin.class)); cards.add(new SetCardInfo("Gelid Shackles", 6, Rarity.COMMON, mage.cards.g.GelidShackles.class)); cards.add(new SetCardInfo("Glacial Plating", 7, Rarity.UNCOMMON, mage.cards.g.GlacialPlating.class)); + cards.add(new SetCardInfo("Goblin Furrier", 82, Rarity.COMMON, mage.cards.g.GoblinFurrier.class)); cards.add(new SetCardInfo("Goblin Rimerunner", 83, Rarity.COMMON, mage.cards.g.GoblinRimerunner.class)); cards.add(new SetCardInfo("Greater Stone Spirit", 84, Rarity.UNCOMMON, mage.cards.g.GreaterStoneSpirit.class)); cards.add(new SetCardInfo("Grim Harvest", 58, Rarity.COMMON, mage.cards.g.GrimHarvest.class)); @@ -119,6 +124,7 @@ public class Coldsnap extends ExpansionSet { cards.add(new SetCardInfo("Kjeldoran Gargoyle", 10, Rarity.UNCOMMON, mage.cards.k.KjeldoranGargoyle.class)); cards.add(new SetCardInfo("Kjeldoran Javelineer", 11, Rarity.COMMON, mage.cards.k.KjeldoranJavelineer.class)); cards.add(new SetCardInfo("Kjeldoran Outrider", 12, Rarity.COMMON, mage.cards.k.KjeldoranOutrider.class)); + cards.add(new SetCardInfo("Kjeldoran War Cry", 13, Rarity.COMMON, mage.cards.k.KjeldoranWarCry.class)); cards.add(new SetCardInfo("Krovikan Mist", 38, Rarity.COMMON, mage.cards.k.KrovikanMist.class)); cards.add(new SetCardInfo("Krovikan Rot", 63, Rarity.UNCOMMON, mage.cards.k.KrovikanRot.class)); cards.add(new SetCardInfo("Krovikan Scoundrel", 64, Rarity.COMMON, mage.cards.k.KrovikanScoundrel.class)); @@ -150,6 +156,7 @@ public class Coldsnap extends ExpansionSet { cards.add(new SetCardInfo("Rimebound Dead", 69, Rarity.COMMON, mage.cards.r.RimeboundDead.class)); cards.add(new SetCardInfo("Rime Transfusion", 68, Rarity.UNCOMMON, mage.cards.r.RimeTransfusion.class)); cards.add(new SetCardInfo("Rimefeather Owl", 42, Rarity.RARE, mage.cards.r.RimefeatherOwl.class)); + cards.add(new SetCardInfo("Rimehorn Aurochs", 118, Rarity.UNCOMMON, mage.cards.r.RimehornAurochs.class)); cards.add(new SetCardInfo("Rimescale Dragon", 95, Rarity.RARE, mage.cards.r.RimescaleDragon.class)); cards.add(new SetCardInfo("Rimewind Cryomancer", 43, Rarity.UNCOMMON, mage.cards.r.RimewindCryomancer.class)); cards.add(new SetCardInfo("Rimewind Taskmage", 44, Rarity.COMMON, mage.cards.r.RimewindTaskmage.class)); @@ -184,12 +191,15 @@ public class Coldsnap extends ExpansionSet { cards.add(new SetCardInfo("Survivor of the Unseen", 48, Rarity.COMMON, mage.cards.s.SurvivorOfTheUnseen.class)); cards.add(new SetCardInfo("Swift Maneuver", 21, Rarity.COMMON, mage.cards.s.SwiftManeuver.class)); cards.add(new SetCardInfo("Tamanoa", 132, Rarity.RARE, mage.cards.t.Tamanoa.class)); + cards.add(new SetCardInfo("Thermal Flux", 49, Rarity.COMMON, mage.cards.t.ThermalFlux.class)); cards.add(new SetCardInfo("Thermopod", 100, Rarity.COMMON, mage.cards.t.Thermopod.class)); cards.add(new SetCardInfo("Thrumming Stone", 142, Rarity.RARE, mage.cards.t.ThrummingStone.class)); cards.add(new SetCardInfo("Tresserhorn Sinks", 150, Rarity.UNCOMMON, mage.cards.t.TresserhornSinks.class)); + cards.add(new SetCardInfo("Tresserhorn Skyknight", 73, Rarity.UNCOMMON, mage.cards.t.TresserhornSkyknight.class)); cards.add(new SetCardInfo("Ursine Fylgja", 22, Rarity.UNCOMMON, mage.cards.u.UrsineFylgja.class)); cards.add(new SetCardInfo("Vanish into Memory", 133, Rarity.UNCOMMON, mage.cards.v.VanishIntoMemory.class)); cards.add(new SetCardInfo("Vexing Sphinx", 50, Rarity.RARE, mage.cards.v.VexingSphinx.class)); + cards.add(new SetCardInfo("Void Maw", 74, Rarity.RARE, mage.cards.v.VoidMaw.class)); cards.add(new SetCardInfo("Wall of Shards", 23, Rarity.UNCOMMON, mage.cards.w.WallOfShards.class)); cards.add(new SetCardInfo("White Shield Crusader", 24, Rarity.UNCOMMON, mage.cards.w.WhiteShieldCrusader.class)); cards.add(new SetCardInfo("Wilderness Elemental", 134, Rarity.UNCOMMON, mage.cards.w.WildernessElemental.class)); From 78d036b6bb29b8d3ddca490b767936f9a89e79f3 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Mon, 1 Jan 2018 18:00:42 +0400 Subject: [PATCH 09/45] UI: fixed windows layer problems when old window overlapped new window (see #4318, #4320 and other) --- .../src/main/java/mage/client/MageFrame.java | 10 +++--- .../mage/client/dialog/AddLandDialog.java | 9 +++++- .../mage/client/dialog/ConnectDialog.java | 4 +-- .../java/mage/client/dialog/MageDialog.java | 32 ++++++++++++++++--- .../mage/client/dialog/PickChoiceDialog.java | 8 +++-- .../mage/client/dialog/PickNumberDialog.java | 11 ++++++- .../mage/client/dialog/PickPileDialog.java | 8 +++++ .../mage/client/dialog/ShowCardsDialog.java | 10 ++++-- .../main/java/mage/client/game/GamePanel.java | 2 +- 9 files changed, 74 insertions(+), 20 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/MageFrame.java b/Mage.Client/src/main/java/mage/client/MageFrame.java index 7df4a984ff0..5d4d579a47e 100644 --- a/Mage.Client/src/main/java/mage/client/MageFrame.java +++ b/Mage.Client/src/main/java/mage/client/MageFrame.java @@ -239,10 +239,10 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { SessionHandler.startSession(this); callbackClient = new CallbackClientImpl(this); connectDialog = new ConnectDialog(); - desktopPane.add(connectDialog, JLayeredPane.POPUP_LAYER); + desktopPane.add(connectDialog, JLayeredPane.MODAL_LAYER); errorDialog = new ErrorDialog(); errorDialog.setLocation(100, 100); - desktopPane.add(errorDialog, JLayeredPane.POPUP_LAYER); + desktopPane.add(errorDialog, JLayeredPane.MODAL_LAYER); UI.addComponent(MageComponents.DESKTOP_PANE, desktopPane); PING_TASK_EXECUTOR.scheduleAtFixedRate(() -> SessionHandler.ping(), 60, 60, TimeUnit.SECONDS); @@ -945,7 +945,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { }//GEN-LAST:event_btnConnectActionPerformed public void btnAboutActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnAboutActionPerformed - JInternalFrame[] windows = desktopPane.getAllFramesInLayer(JLayeredPane.POPUP_LAYER); + JInternalFrame[] windows = desktopPane.getAllFramesInLayer(JLayeredPane.MODAL_LAYER); for (JInternalFrame window : windows) { if (window instanceof AboutDialog) { // don't open the window twice. @@ -953,7 +953,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { } } AboutDialog aboutDialog = new AboutDialog(); - desktopPane.add(aboutDialog, JLayeredPane.POPUP_LAYER); + desktopPane.add(aboutDialog, JLayeredPane.MODAL_LAYER); aboutDialog.showDialog(VERSION); }//GEN-LAST:event_btnAboutActionPerformed @@ -1096,7 +1096,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { public void showUserRequestDialog(final UserRequestMessage userRequestMessage) { final UserRequestDialog userRequestDialog = new UserRequestDialog(); userRequestDialog.setLocation(100, 100); - desktopPane.add(userRequestDialog, JLayeredPane.POPUP_LAYER); + desktopPane.add(userRequestDialog, JLayeredPane.MODAL_LAYER); if (SwingUtilities.isEventDispatchThread()) { userRequestDialog.showDialog(userRequestMessage); } else { diff --git a/Mage.Client/src/main/java/mage/client/dialog/AddLandDialog.java b/Mage.Client/src/main/java/mage/client/dialog/AddLandDialog.java index ed9742124f7..cb39ef8eb85 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/AddLandDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/AddLandDialog.java @@ -114,7 +114,14 @@ public class AddLandDialog extends MageDialog { } cbLandSet.setModel(new DefaultComboBoxModel(landSetNames.toArray())); - MageFrame.getDesktop().add(this, JLayeredPane.PALETTE_LAYER); + + // windows settings + if (this.isModal()){ + MageFrame.getDesktop().add(this, JLayeredPane.MODAL_LAYER); + }else{ + MageFrame.getDesktop().add(this, JLayeredPane.PALETTE_LAYER); + } + this.setVisible(true); } 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 7a453e38435..867698822d2 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/ConnectDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/ConnectDialog.java @@ -102,10 +102,10 @@ public class ConnectDialog extends MageDialog { this.txtPassword.addActionListener(connectAction); registerUserDialog = new RegisterUserDialog(this); - MageFrame.getDesktop().add(registerUserDialog, JLayeredPane.POPUP_LAYER); + MageFrame.getDesktop().add(registerUserDialog, JLayeredPane.MODAL_LAYER); resetPasswordDialog = new ResetPasswordDialog(this); - MageFrame.getDesktop().add(resetPasswordDialog, JLayeredPane.POPUP_LAYER); + MageFrame.getDesktop().add(resetPasswordDialog, JLayeredPane.MODAL_LAYER); } public void showDialog() { diff --git a/Mage.Client/src/main/java/mage/client/dialog/MageDialog.java b/Mage.Client/src/main/java/mage/client/dialog/MageDialog.java index ef6e29b4268..5deaed81aa3 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/MageDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/MageDialog.java @@ -45,8 +45,8 @@ import java.awt.event.MouseEvent; import java.beans.PropertyVetoException; import java.lang.reflect.InvocationTargetException; import java.util.logging.Level; -import javax.swing.JComponent; -import javax.swing.SwingUtilities; +import javax.swing.*; + import mage.client.MageFrame; import org.apache.log4j.Logger; @@ -74,11 +74,34 @@ public class MageDialog extends javax.swing.JInternalFrame { @Override public void show() { super.show(); - this.toFront(); + + // frames desktop ordering + // more info https://docs.oracle.com/javase/7/docs/api/javax/swing/JLayeredPane.html + // WARNING, use + // - JLayeredPane.DEFAULT_LAYER: tables and games (tabs) + // - JLayeredPane.PALETTE_LAYER: toolbars and info windows like cards list, not modal dialogs (not required user actions) + // - JLayeredPane.MODAL_LAYER: all modal dialogs (user required actions - select cards in game, new game window, error windows) + // - JLayeredPane.POPUP_LAYER: hints and other top level graphics + // - JLayeredPane.DRAG_LAYER: top most layer for critical actions and user controls + /* + JInternalFrame[] frames = MageFrame.getDesktop().getAllFrames(); + System.out.println("---"); + for(JInternalFrame frame: frames){ + int zorder = -1; + if (frame.getParent() != null){ + frame.getParent().getComponentZOrder(frame); + } + System.out.println(frame.getClass() + " (" + frame.getTitle() + ") : layer = " + frame.getLayer() + ", zorder = " + zorder); + } + */ + if (modal) { this.setClosable(false); } - if (this.modal) { + + this.toFront(); + + if (modal){ startModal(); } } @@ -108,7 +131,6 @@ public class MageDialog extends javax.swing.JInternalFrame { } private synchronized void startModal() { - try { if (SwingUtilities.isEventDispatchThread()) { EventQueue theQueue = getToolkit().getSystemEventQueue(); 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 60cadbfa3d0..80a28468f9e 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.java @@ -165,7 +165,11 @@ public class PickChoiceDialog extends MageDialog { } // window settings - MageFrame.getDesktop().add(this, JLayeredPane.PALETTE_LAYER); + if (this.isModal()){ + MageFrame.getDesktop().add(this, JLayeredPane.MODAL_LAYER); + }else{ + MageFrame.getDesktop().add(this, JLayeredPane.PALETTE_LAYER); + } if (mageDialogState != null) { mageDialogState.setStateToDialog(this); @@ -174,7 +178,7 @@ public class PickChoiceDialog extends MageDialog { this.setLocation(centered.x, centered.y); GuiDisplayUtil.keepComponentInsideScreen(centered.x, centered.y, this); } - + // final load loadData(); diff --git a/Mage.Client/src/main/java/mage/client/dialog/PickNumberDialog.java b/Mage.Client/src/main/java/mage/client/dialog/PickNumberDialog.java index 018c2bb44ec..f4e3244fb19 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PickNumberDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/PickNumberDialog.java @@ -35,7 +35,9 @@ package mage.client.dialog; import java.awt.Point; -import javax.swing.SpinnerNumberModel; +import javax.swing.*; + +import mage.client.MageFrame; import mage.client.util.SettingsManager; import mage.client.util.gui.GuiDisplayUtil; @@ -60,6 +62,13 @@ public class PickNumberDialog extends MageDialog { this.btnCancel.setVisible(false); this.pack(); + // window settings + if (this.isModal()){ + MageFrame.getDesktop().add(this, JLayeredPane.MODAL_LAYER); + }else{ + MageFrame.getDesktop().add(this, JLayeredPane.PALETTE_LAYER); + } + Point centered = SettingsManager.instance.getComponentPosition(getWidth(), getHeight()); this.setLocation(centered.x, centered.y); GuiDisplayUtil.keepComponentInsideScreen(centered.x, centered.y, this); diff --git a/Mage.Client/src/main/java/mage/client/dialog/PickPileDialog.java b/Mage.Client/src/main/java/mage/client/dialog/PickPileDialog.java index 9fcfdfdd24b..ff3682272ca 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PickPileDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/PickPileDialog.java @@ -114,6 +114,14 @@ public class PickPileDialog extends MageDialog { this.revalidate(); this.repaint(); this.setModal(true); + + // window settings + if (this.isModal()){ + MageFrame.getDesktop().add(this, JLayeredPane.MODAL_LAYER); + }else{ + MageFrame.getDesktop().add(this, JLayeredPane.PALETTE_LAYER); + } + this.setVisible(true); } diff --git a/Mage.Client/src/main/java/mage/client/dialog/ShowCardsDialog.java b/Mage.Client/src/main/java/mage/client/dialog/ShowCardsDialog.java index d26d318012b..f185bf2556f 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/ShowCardsDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/ShowCardsDialog.java @@ -120,15 +120,19 @@ public class ShowCardsDialog extends MageDialog { this.cardArea.addCardEventListener(eventListener); } - if (getParent() != MageFrame.getDesktop() /*|| this.isClosed*/) { - MageFrame.getDesktop().add(this, JLayeredPane.DEFAULT_LAYER); - } pack(); this.revalidate(); this.repaint(); this.setModal(modal); + // window settings + if (this.isModal()){ + MageFrame.getDesktop().add(this, JLayeredPane.MODAL_LAYER); + }else{ + MageFrame.getDesktop().add(this, JLayeredPane.PALETTE_LAYER); + } + SwingUtilities.invokeLater(() -> { if (!positioned) { int width = ShowCardsDialog.this.getWidth(); 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 ab0daf3e706..3509af9c288 100644 --- a/Mage.Client/src/main/java/mage/client/game/GamePanel.java +++ b/Mage.Client/src/main/java/mage/client/game/GamePanel.java @@ -1115,7 +1115,7 @@ public final class GamePanel extends javax.swing.JPanel { } this.feedbackPanel.getFeedback(required ? FeedbackMode.INFORM : FeedbackMode.CANCEL, message, gameView.getSpecial(), options0, messageId); if (dialog != null) { - this.pickTarget.add(dialog); + this.pickTarget.add(dialog); // TODO: 01.01.2018, JayDi85: why feedbackPanel saved to pickTarget list? Need to research } } From 56d214f0b25f142e3680f8fee098fc56d08fa40e Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Mon, 1 Jan 2018 18:02:20 +0400 Subject: [PATCH 10/45] UI: added set search button in new tournament dialog --- .../client/dialog/NewTournamentDialog.java | 124 ++++++++++++++---- 1 file changed, 95 insertions(+), 29 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java b/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java index 5842ca75146..17a376f4dca 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java @@ -47,6 +47,7 @@ import mage.cards.repository.ExpansionRepository; import mage.client.MageFrame; import mage.client.SessionHandler; import mage.client.table.TournamentPlayerPanel; +import mage.client.util.gui.FastSearchUtil; import mage.constants.MatchTimeLimit; import mage.constants.MultiplayerAttackOption; import mage.constants.RangeOfInfluence; @@ -76,7 +77,7 @@ public class NewTournamentDialog extends MageDialog { private RandomPacksSelectorDialog randomPackSelector; private JTextArea txtRandomPacks; private final List players = new ArrayList<>(); - private final List packs = new ArrayList<>(); + private final List packPanels = new ArrayList<>(); private static final int CONSTRUCTION_TIME_MIN = 6; private static final int CONSTRUCTION_TIME_MAX = 30; private boolean isRandom = false; @@ -586,8 +587,13 @@ public class NewTournamentDialog extends MageDialog { tOptions.getLimitedOptions().getSetCodes().addAll(selected); } } else { - for (JComboBox pack : packs) { - tOptions.getLimitedOptions().getSetCodes().add(((ExpansionInfo) pack.getSelectedItem()).getCode()); + for (JPanel panel : packPanels) { + JComboBox combo = findComboInComponent(panel); + if(combo != null) { + tOptions.getLimitedOptions().getSetCodes().add(((ExpansionInfo) combo.getSelectedItem()).getCode()); + }else{ + logger.error("Can't find combo component in " + panel.toString()); + } } } tOptions.getMatchOptions().setDeckType("Limited"); @@ -884,35 +890,89 @@ public class NewTournamentDialog extends MageDialog { } private void createPacks(int numPacks) { - while (packs.size() > numPacks) { - pnlPacks.remove(packs.get(packs.size() - 1)); - packs.remove(packs.size() - 1); + while (packPanels.size() > numPacks) { + pnlPacks.remove(packPanels.get(packPanels.size() - 1)); + packPanels.remove(packPanels.size() - 1); } - while (packs.size() < numPacks) { + while (packPanels.size() < numPacks) { + // SELECT PACK + // panel + JPanel setPanel = new JPanel(); + setPanel.setLayout(new javax.swing.BoxLayout(setPanel, javax.swing.BoxLayout.LINE_AXIS)); + setPanel.setOpaque(false); + //setPanel.setPreferredSize(new Dimension(200, 25)); + //setPanel.setMaximumSize(new Dimension(200, 25)); + pnlPacks.add(setPanel); + packPanels.add(setPanel); // for later access + // combo set JComboBox pack = new JComboBox(); + pack = new JComboBox(); pack.setModel(new DefaultComboBoxModel(ExpansionRepository.instance.getWithBoostersSortedByReleaseDate())); - pnlPacks.add(pack); - packs.add(pack); pack.addActionListener(evt -> packActionPerformed(evt)); + pack.setAlignmentX(0.0F); + pack.setMinimumSize(new Dimension(50, 25)); + pack.setPreferredSize(new Dimension(50, 25)); + pack.setMaximumSize(new Dimension(Integer.MAX_VALUE, 25)); + setPanel.add(pack); + // search button + JButton searchButton = new JButton(); + searchButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/buttons/search_24.png"))); + searchButton.setToolTipText("Search and select from list"); + searchButton.setAlignmentX(1.0F); + searchButton.setMinimumSize(new java.awt.Dimension(24, 24)); + searchButton.setPreferredSize(new java.awt.Dimension(32, 32)); + searchButton.setMaximumSize(new java.awt.Dimension(32, 32)); + searchButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + + // search combo box near button (must be only one combo in panel) + JButton button = (JButton)evt.getSource(); + JComboBox combo = findComboInComponent(button.getParent()); + + if (combo != null) { + FastSearchUtil.showFastSearchForStringComboBox(combo, "Select value"); + } + } + }); + setPanel.add(searchButton); } this.pack(); this.revalidate(); this.repaint(); } - private void packActionPerformed(java.awt.event.ActionEvent evt) { - boolean start = false; - int selectedIndex = 0; - for (JComboBox pack : packs) { - if (!start) { - if (evt.getSource().equals(pack)) { - start = true; - selectedIndex = pack.getSelectedIndex(); - } - } else { - pack.setSelectedIndex(selectedIndex); + private JComboBox findComboInComponent(Container panel){ + // search combo box near button (must be only one combo in panel) + JComboBox combo = null; + for(Component comp: panel.getComponents()){ + if (comp instanceof JComboBox){ + combo = (JComboBox)comp; + break; } } + return combo; + } + + private void packActionPerformed(java.awt.event.ActionEvent evt) { + // fill all bottom combobox with same value + JComboBox curentCombo = (JComboBox)evt.getSource(); + int newValue = curentCombo.getSelectedIndex(); + + // search start index + int startIndex = 0; + for(int i = 0; i < packPanels.size(); i++){ + JComboBox pack = findComboInComponent(packPanels.get(i)); + if (pack.equals(curentCombo)){ + startIndex = i + 1; + break; + } + } + + // change all from start index + for(int i = startIndex; i < packPanels.size(); i++){ + JComboBox pack = findComboInComponent(packPanels.get(i)); + pack.setSelectedIndex(newValue); + } } private void createPlayers(int numPlayers) { @@ -1054,16 +1114,22 @@ public class NewTournamentDialog extends MageDialog { int packNumber = 0; for (String pack : packsArray) { packNumber++; - if (this.packs.size() >= packNumber - 1) { - JComboBox comboBox = this.packs.get(packNumber - 1); - ComboBoxModel model = comboBox.getModel(); - int size = model.getSize(); - for (int i = 0; i < size; i++) { - ExpansionInfo element = (ExpansionInfo) model.getElementAt(i); - if (element.getCode().equals(pack.trim())) { - comboBox.setSelectedIndex(i); - break; + if (this.packPanels.size() >= packNumber - 1) { + JPanel panel = packPanels.get(packNumber - 1); + JComboBox comboBox = findComboInComponent(panel); + + if (comboBox != null) { + ComboBoxModel model = comboBox.getModel(); + int size = model.getSize(); + for (int i = 0; i < size; i++) { + ExpansionInfo element = (ExpansionInfo) model.getElementAt(i); + if (element.getCode().equals(pack.trim())) { + comboBox.setSelectedIndex(i); + break; + } } + }else{ + logger.error("Can't find combo component in " + panel.toString()); } } From 1ad8529df8ea1afd628a311ac68227d3cde01f9b Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Mon, 1 Jan 2018 20:51:56 +0400 Subject: [PATCH 11/45] Fixed chrismas time fail on new year :-( --- .../src/main/java/mage/client/MageFrame.java | 17 +++++--- .../java/mage/client/util/ChrismasTest.java | 42 +++++++++++++++++++ 2 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 Mage.Client/src/test/java/mage/client/util/ChrismasTest.java diff --git a/Mage.Client/src/main/java/mage/client/MageFrame.java b/Mage.Client/src/main/java/mage/client/MageFrame.java index 5d4d579a47e..9c5d93d6fe4 100644 --- a/Mage.Client/src/main/java/mage/client/MageFrame.java +++ b/Mage.Client/src/main/java/mage/client/MageFrame.java @@ -429,16 +429,21 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { } } - private boolean isChrismasTime(){ + public static boolean isChrismasTime(Date currentTime){ // from december 15 to january 15 - Calendar cal = Calendar.getInstance(); + Calendar cal = new GregorianCalendar(); + cal.setTime(currentTime); + int currentYear = cal.get(Calendar.YEAR); - Date currentTime = cal.getTime(); + if (cal.get(Calendar.MONTH) == Calendar.JANUARY){ + currentYear = currentYear - 1; + } Date chrisFrom = new GregorianCalendar(currentYear, Calendar.DECEMBER, 15).getTime(); - Date chrisTo = new GregorianCalendar(currentYear + 1, Calendar.JANUARY, 15 + 1).getTime(); + Date chrisTo = new GregorianCalendar(currentYear + 1, Calendar.JANUARY, 15 + 1).getTime(); // end of the 15 day - return currentTime.after(chrisFrom) && currentTime.before(chrisTo); + return ((currentTime.equals(chrisFrom) || currentTime.after(chrisFrom)) + && currentTime.before(chrisTo)); } private void addMageLabel() { @@ -448,7 +453,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { String filename; float ratio; - if (isChrismasTime()){ + if (isChrismasTime(Calendar.getInstance().getTime())){ // chrismass logo LOGGER.info("Yo Ho Ho, Merry Christmas and a Happy New Year"); filename = "/label-xmage-christmas.png"; diff --git a/Mage.Client/src/test/java/mage/client/util/ChrismasTest.java b/Mage.Client/src/test/java/mage/client/util/ChrismasTest.java new file mode 100644 index 00000000000..93d4b26c708 --- /dev/null +++ b/Mage.Client/src/test/java/mage/client/util/ChrismasTest.java @@ -0,0 +1,42 @@ +package mage.client.util; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; + +import static mage.client.MageFrame.isChrismasTime; + +public class ChrismasTest { + + private Date getDate(int Year, int Month, int Day){ + Calendar cal = new GregorianCalendar(Year, Month - 1, Day); + return cal.getTime(); + } + + @Test + public void ignoreDefaultResponse() throws Exception { + // chrismas from 15 december to 15 january + Assert.assertEquals(false, isChrismasTime(getDate(2017, 11, 1))); + Assert.assertEquals(false, isChrismasTime(getDate(2017, 11, 15))); + Assert.assertEquals(false, isChrismasTime(getDate(2017, 11, 30))); + Assert.assertEquals(false, isChrismasTime(getDate(2017, 12, 1))); + Assert.assertEquals(false, isChrismasTime(getDate(2017, 12, 14))); + Assert.assertEquals(true, isChrismasTime(getDate(2017, 12, 15))); + Assert.assertEquals(true, isChrismasTime(getDate(2017, 12, 16))); + Assert.assertEquals(true, isChrismasTime(getDate(2017, 12, 31))); + Assert.assertEquals(true, isChrismasTime(getDate(2018, 1, 1))); + Assert.assertEquals(true, isChrismasTime(getDate(2018, 1, 14))); + Assert.assertEquals(true, isChrismasTime(getDate(2018, 1, 15))); + Assert.assertEquals(false, isChrismasTime(getDate(2018, 1, 16))); + Assert.assertEquals(false, isChrismasTime(getDate(2018, 1, 31))); + Assert.assertEquals(false, isChrismasTime(getDate(2018, 2, 1))); + Assert.assertEquals(false, isChrismasTime(getDate(2018, 12, 1))); + Assert.assertEquals(true, isChrismasTime(getDate(2018, 12, 20))); + Assert.assertEquals(true, isChrismasTime(getDate(2019, 1, 10))); + Assert.assertEquals(false, isChrismasTime(getDate(2019, 1, 25))); + } + +} From c08c5a149b129b4478e2213b9c9dabbf43ea1e36 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Mon, 1 Jan 2018 18:11:49 +0100 Subject: [PATCH 12/45] Fixed Heroism ability tax cost --- Mage.Sets/src/mage/cards/h/Heroism.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/cards/h/Heroism.java b/Mage.Sets/src/mage/cards/h/Heroism.java index 34ac472c957..cb1c9ef02bf 100644 --- a/Mage.Sets/src/mage/cards/h/Heroism.java +++ b/Mage.Sets/src/mage/cards/h/Heroism.java @@ -112,6 +112,7 @@ class HeroismEffect extends OneShotEffect { Cost cost = new ManaCostsImpl("{2}{R}"); List permanentsToPrevent = new ArrayList<>(); for (Permanent permanent : game.getState().getBattlefield().getAllActivePermanents(filter, player.getId(), game)) { + cost.clearPaid(); String message = "Pay " + cost.getText() + "? If you don't, " + permanent.getLogName() + "'s combat damage will be prevented this turn."; if (player != null && player.chooseUse(Outcome.Neutral, message, source, game)) { if (cost.pay(source, game, source.getSourceId(), player.getId(), false, null)) { From 2fcbd6535f72d1e8abedb35544face6d14a470e9 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Mon, 1 Jan 2018 18:15:09 +0100 Subject: [PATCH 13/45] Implemented Merseine --- Mage.Sets/src/mage/cards/m/Merseine.java | 172 +++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/m/Merseine.java diff --git a/Mage.Sets/src/mage/cards/m/Merseine.java b/Mage.Sets/src/mage/cards/m/Merseine.java new file mode 100644 index 00000000000..dca22e5da9d --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/Merseine.java @@ -0,0 +1,172 @@ +/* + * 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.m; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.SourceHasCounterCondition; +import mage.abilities.decorator.ConditionalContinuousRuleModifyingEffect; +import mage.abilities.costs.Cost; +import mage.abilities.costs.CostImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.Effects; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.DontUntapInControllersUntapStepEnchantedEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.effects.common.counter.RemoveCounterSourceEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author L_J + */ +public class Merseine extends CardImpl { + + public Merseine(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{2}{U}{U}"); + this.subtype.add(SubType.AURA); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.Detriment)); + this.addAbility(new EnchantAbility(auraTarget.getTargetName())); + + // Merseine enters the battlefield with three net counters on it. + Effect effect = new AddCountersSourceEffect(CounterType.ECHO.createInstance(3)); + effect.setText("with three net counters on it"); + this.addAbility(new EntersBattlefieldAbility(effect)); + + // Enchanted creature doesn't untap during its controller's untap step if Merseine has a net counter on it. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousRuleModifyingEffect(new DontUntapInControllersUntapStepEnchantedEffect(), + new SourceHasCounterCondition(CounterType.ECHO)).setText("Enchanted creature doesn't untap during its controller's untap step if Merseine has a net counter on it"))); + + // Pay enchanted creature's mana cost: Remove a net counter from Merseine. Any player may activate this ability, but only if he or she controls the enchanted creature. + SimpleActivatedAbility ability = new MerseineActivatedAbility(); + ability.setMayActivate(TargetController.ANY); + this.addAbility(ability); + } + + public Merseine(final Merseine card) { + super(card); + } + + @Override + public Merseine copy() { + return new Merseine(this); + } +} + +class MerseineActivatedAbility extends SimpleActivatedAbility { + + public MerseineActivatedAbility() { + super(Zone.BATTLEFIELD, new RemoveCounterSourceEffect(CounterType.ECHO.createInstance()), new MerseineCost()); + } + + private MerseineActivatedAbility(final MerseineActivatedAbility ability) { + super(ability); + } + + @Override + public Effects getEffects(Game game, EffectType effectType) { + return super.getEffects(game, effectType); + } + + @Override + public boolean canActivate(UUID playerId, Game game) { + Permanent sourcePermanent = game.getBattlefield().getPermanent(this.getSourceId()); + if (sourcePermanent != null) { + Permanent attachedTo = game.getPermanent(sourcePermanent.getAttachedTo()); + if (attachedTo != null) { + return super.canActivate(attachedTo.getControllerId(), game); + } + } + return false; + } + + @Override + public MerseineActivatedAbility copy() { + return new MerseineActivatedAbility(this); + } + + @Override + public String getRule() { + return "Pay enchanted creature's mana cost: Remove a net counter from Merseine. Any player may activate this ability, but only if he or she controls the enchanted creature."; + } +} + +class MerseineCost extends CostImpl { + + public MerseineCost() { + this.text = "Pay enchanted creature's mana cost"; + } + + public MerseineCost(final MerseineCost cost) { + super(cost); + } + + @Override + public MerseineCost copy() { + return new MerseineCost(this); + } + + @Override + public boolean canPay(Ability ability, UUID sourceId, UUID controllerId, Game game) { + Permanent sourcePermanent = game.getBattlefield().getPermanent(sourceId); + if (sourcePermanent != null) { + Permanent attachedTo = game.getPermanent(sourcePermanent.getAttachedTo()); + if (attachedTo != null) { + return attachedTo.getManaCost().canPay(ability, sourceId, controllerId, game); + } + } + return false; + } + + @Override + public boolean pay(Ability ability, Game game, UUID sourceId, UUID controllerId, boolean noMana, Cost costToPay) { + Permanent sourcePermanent = game.getBattlefield().getPermanent(sourceId); + if (sourcePermanent != null) { + Permanent attachedTo = game.getPermanent(sourcePermanent.getAttachedTo()); + if (attachedTo != null) { + paid = attachedTo.getManaCost().pay(ability, game, sourceId, controllerId, noMana); + } + } + return paid; + } +} From dce22b38524ff788e32a88161ec9582b034c070b Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Mon, 1 Jan 2018 18:15:59 +0100 Subject: [PATCH 14/45] Implemented Raiding Party --- Mage.Sets/src/mage/cards/r/RaidingParty.java | 160 +++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/r/RaidingParty.java diff --git a/Mage.Sets/src/mage/cards/r/RaidingParty.java b/Mage.Sets/src/mage/cards/r/RaidingParty.java new file mode 100644 index 00000000000..2db0dfd4c10 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RaidingParty.java @@ -0,0 +1,160 @@ +/* + * 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.HashSet; +import java.util.Set; +import java.util.UUID; +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CantBeTargetedSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.FilterObject; +import mage.filter.FilterStackObject; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.ColorPredicate; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.Target; +import mage.target.TargetPermanent; +import mage.target.common.TargetControlledCreaturePermanent; + +/** + * + * @author L_J + */ +public class RaidingParty extends CardImpl { + + private static final FilterObject filterWhite = new FilterStackObject("white spells or abilities from white sources"); + private static final FilterControlledCreaturePermanent filterOrc = new FilterControlledCreaturePermanent("an Orc"); + + static { + filterWhite.add(new ColorPredicate(ObjectColor.WHITE)); + filterOrc.add(new SubtypePredicate(SubType.ORC)); + } + + public RaidingParty(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{2}{R}"); + + // Raiding Party can't be the target of white spells or abilities from white sources. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new CantBeTargetedSourceEffect(filterWhite, Duration.WhileOnBattlefield))); + + // Sacrifice an Orc: Each player may tap any number of untapped white creatures he or she controls. For each creature tapped this way, that player chooses up to two Plains. Then destroy all Plains that weren't chosen this way by any player. + this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new RaidingPartyEffect(), new SacrificeTargetCost(new TargetControlledCreaturePermanent(1,1, filterOrc, true)))); + } + + public RaidingParty(final RaidingParty card) { + super(card); + } + + @Override + public RaidingParty copy() { + return new RaidingParty(this); + } +} + +class RaidingPartyEffect extends OneShotEffect { + + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("untapped white creatures"); + private static final FilterPermanent filter2 = new FilterPermanent("Plains"); + + static { + filter.add(Predicates.not(new TappedPredicate())); + filter.add(new ColorPredicate(ObjectColor.WHITE)); + filter2.add(new SubtypePredicate(SubType.PLAINS)); + } + + RaidingPartyEffect() { + super(Outcome.Detriment); + staticText = "Each player may tap any number of untapped white creatures he or she controls. For each creature tapped this way, that player chooses up to two Plains. Then destroy all Plains that weren't chosen this way by any player"; + } + + RaidingPartyEffect(RaidingPartyEffect effect) { + super(effect); + } + + @Override + public RaidingPartyEffect copy() { + return new RaidingPartyEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); + if (sourcePermanent != null) { + Set plainsToSave = new HashSet<>(); + for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { + Player player = game.getPlayer(playerId); + if (player != null) { + int countBattlefield = game.getBattlefield().getAllActivePermanents(filter, game.getActivePlayerId(), game).size(); + int tappedCount = 0; + Target untappedCreatureTarget = new TargetControlledCreaturePermanent(0, Integer.MAX_VALUE, filter, true); + if (player.choose(Outcome.Benefit, untappedCreatureTarget, source.getSourceId(), game)) { + tappedCount = untappedCreatureTarget.getTargets().size(); + for (UUID creatureId : untappedCreatureTarget.getTargets()) { + Permanent creature = game.getPermanentOrLKIBattlefield(creatureId); + if (creature != null) { + creature.tap(game); + } + } + } + if (tappedCount > 0) { + Target plainsToSaveTarget = new TargetPermanent(0, tappedCount * 2, filter2, true); + if (player.choose(Outcome.Benefit, plainsToSaveTarget, source.getSourceId(), game)) { + for (UUID plainsId : plainsToSaveTarget.getTargets()) { + plainsToSave.add(plainsId); + } + } + } + } + } + for (Permanent plains : game.getBattlefield().getActivePermanents(filter2, source.getControllerId(), source.getSourceId(), game)) { + if (!plainsToSave.contains(plains.getId())) { + plains.destroy(source.getSourceId(), game, false); + } + } + return true; + } + return false; + } +} From f66f5cf2185d72e1a026198bc22477477e10e9e7 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Mon, 1 Jan 2018 18:16:36 +0100 Subject: [PATCH 15/45] Implemented Tidal Flats --- Mage.Sets/src/mage/cards/t/TidalFlats.java | 144 +++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/t/TidalFlats.java diff --git a/Mage.Sets/src/mage/cards/t/TidalFlats.java b/Mage.Sets/src/mage/cards/t/TidalFlats.java new file mode 100644 index 00000000000..d33db8fa398 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TidalFlats.java @@ -0,0 +1,144 @@ +/* + * 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.t; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.common.FilterAttackingCreature; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.game.Game; +import mage.game.combat.CombatGroup; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author L_J + */ +public class TidalFlats extends CardImpl { + + public TidalFlats(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{U}"); + + // {U}{U}: For each attacking creature without flying, its controller may pay {1}. If he or she doesn't, creatures you control blocking that creature gain first strike until end of turn. + this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new TidalFlatsEffect(), new ManaCostsImpl("{U}{U}"))); + } + + public TidalFlats(final TidalFlats card) { + super(card); + } + + @Override + public TidalFlats copy() { + return new TidalFlats(this); + } +} + +class TidalFlatsEffect extends OneShotEffect { + + private static final FilterAttackingCreature filter = new FilterAttackingCreature("attacking creature without flying"); + static { + filter.add(Predicates.not(new AbilityPredicate(FlyingAbility.class))); + } + + public TidalFlatsEffect() { + super(Outcome.Benefit); + this.staticText = "For each attacking creature without flying, its controller may pay {1}. If he or she doesn't, creatures you control blocking that creature gain first strike until end of turn"; + } + + public TidalFlatsEffect(final TidalFlatsEffect effect) { + super(effect); + } + + @Override + public TidalFlatsEffect copy() { + return new TidalFlatsEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + game.getPlayerList(); + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + Player player = game.getPlayer(game.getActivePlayerId()); + Cost cost = new ManaCostsImpl("{1}"); + List affectedPermanents = new ArrayList<>(); + for (Permanent permanent : game.getState().getBattlefield().getAllActivePermanents(filter, player.getId(), game)) { + cost.clearPaid(); + String message = "Pay " + cost.getText() + " for " + permanent.getLogName() + "? If you don't, creatures " + controller.getLogName() + " controls blocking it gain first strike until end of turn."; + if (player != null && player.chooseUse(Outcome.Benefit, message, source, game)) { + if (cost.pay(source, game, source.getSourceId(), player.getId(), false, null)) { + game.informPlayers(player.getLogName() + " paid " + cost.getText() + " for " + permanent.getLogName()); + continue; + } else { + game.informPlayers(player.getLogName() + " didn't pay " + cost.getText() + " for " + permanent.getLogName()); + affectedPermanents.add(permanent); + } + } else { + game.informPlayers(player.getLogName() + " didn't pay " + cost.getText() + " for " + permanent.getLogName()); + affectedPermanents.add(permanent); + } + } + + for (Permanent permanent : affectedPermanents) { + CombatGroup group = game.getCombat().findGroup(permanent.getId()); + if (group != null) { + for (UUID blockerId : group.getBlockers()) { + Permanent blocker = game.getPermanent(blockerId); + if (blocker != null && blocker.getControllerId() == controller.getId()) { + ContinuousEffect effect = new GainAbilityTargetEffect(FirstStrikeAbility.getInstance(), Duration.EndOfTurn); + effect.setTargetPointer(new FixedTarget(blocker.getId())); + game.addEffect(effect, source); + } + } + } + + } + return true; + } + return false; + } +} From ec8d2032ebe4e31d0ffca30090980c0f664bf986 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Mon, 1 Jan 2018 18:17:19 +0100 Subject: [PATCH 16/45] Implemented Thelon's Chant --- Mage.Sets/src/mage/cards/t/ThelonsChant.java | 126 +++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/t/ThelonsChant.java diff --git a/Mage.Sets/src/mage/cards/t/ThelonsChant.java b/Mage.Sets/src/mage/cards/t/ThelonsChant.java new file mode 100644 index 00000000000..578450e8b1a --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/ThelonsChant.java @@ -0,0 +1,126 @@ +/* + * 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.t; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.costs.CostImpl; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.SacrificeSourceUnlessPaysEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Outcome; +import mage.constants.SetTargetPointer; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetControlledCreaturePermanent; + +/** + * + * @author L_J + */ +public class ThelonsChant extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("a Swamp"); + + static{ + filter.add(new SubtypePredicate(SubType.SWAMP)); + } + + public ThelonsChant(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{G}{G}"); + + // At the beginning of your upkeep, sacrifice Thelon's Chant unless you pay {G}. + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new SacrificeSourceUnlessPaysEffect(new ManaCostsImpl("{G}")), TargetController.YOU, false)); + + // Whenever a player puts a Swamp onto the battlefield, Thelon's Chant deals 3 damage to that player unless he or she puts a -1/-1 counter on a creature he or she controls. + this.addAbility(new EntersBattlefieldAllTriggeredAbility(Zone.BATTLEFIELD, new ThelonsChantEffect(), filter, false, SetTargetPointer.PLAYER, + "Whenever a player puts a Swamp onto the battlefield, {this} deals 3 damage to that player unless he or she puts a -1/-1 counter on a creature he or she controls.")); + } + + public ThelonsChant(final ThelonsChant card) { + super(card); + } + + @Override + public ThelonsChant copy() { + return new ThelonsChant(this); + } +} + +class ThelonsChantEffect extends OneShotEffect { + + public ThelonsChantEffect() { + super(Outcome.Damage); + staticText = "{this} deals 3 damage to that player unless he or she puts a -1/-1 counter on a creature he or she controls"; + } + + public ThelonsChantEffect(final ThelonsChantEffect effect) { + super(effect); + } + + @Override + public ThelonsChantEffect copy() { + return new ThelonsChantEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); + if (player != null && sourcePermanent != null) { + boolean paid = false; + TargetControlledCreaturePermanent target = new TargetControlledCreaturePermanent(); + target.setNotTarget(true); + if (player.chooseUse(Outcome.Detriment, "Put a -1/-1 counter on a creature you control? (otherwise " + sourcePermanent.getLogName() + " deals 3 damage to you)", source, game) + && player.choose(Outcome.UnboostCreature, target, source.getSourceId(), game)) { + Permanent permanent = game.getPermanent(target.getFirstTarget()); + if (permanent != null) { + permanent.addCounters(CounterType.M1M1.createInstance(), source, game); + paid = true; + } + } + if (!paid) { + player.damage(3, source.getSourceId(), game, false, true); + } + return true; + } + return false; + } +} From 2421953efb51a80b605ec93b923c48552eac8101 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Mon, 1 Jan 2018 18:18:01 +0100 Subject: [PATCH 17/45] Implemented Tourach's Chant --- Mage.Sets/src/mage/cards/t/TourachsChant.java | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/t/TourachsChant.java diff --git a/Mage.Sets/src/mage/cards/t/TourachsChant.java b/Mage.Sets/src/mage/cards/t/TourachsChant.java new file mode 100644 index 00000000000..53219c5ee95 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TourachsChant.java @@ -0,0 +1,126 @@ +/* + * 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.t; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.costs.CostImpl; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.SacrificeSourceUnlessPaysEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Outcome; +import mage.constants.SetTargetPointer; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetControlledCreaturePermanent; + +/** + * + * @author L_J + */ +public class TourachsChant extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("a Forest"); + + static{ + filter.add(new SubtypePredicate(SubType.FOREST)); + } + + public TourachsChant(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{B}{B}"); + + // At the beginning of your upkeep, sacrifice Tourach's Chant unless you pay {B}. + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new SacrificeSourceUnlessPaysEffect(new ManaCostsImpl("{B}")), TargetController.YOU, false)); + + // Whenever a player puts a Forest onto the battlefield, Tourach's Chant deals 3 damage to that player unless he or she puts a -1/-1 counter on a creature he or she controls. + this.addAbility(new EntersBattlefieldAllTriggeredAbility(Zone.BATTLEFIELD, new TourachsChantEffect(), filter, false, SetTargetPointer.PLAYER, + "Whenever a player puts a Forest onto the battlefield, {this} deals 3 damage to that player unless he or she puts a -1/-1 counter on a creature he or she controls.")); + } + + public TourachsChant(final TourachsChant card) { + super(card); + } + + @Override + public TourachsChant copy() { + return new TourachsChant(this); + } +} + +class TourachsChantEffect extends OneShotEffect { + + public TourachsChantEffect() { + super(Outcome.Damage); + staticText = "{this} deals 3 damage to that player unless he or she puts a -1/-1 counter on a creature he or she controls"; + } + + public TourachsChantEffect(final TourachsChantEffect effect) { + super(effect); + } + + @Override + public TourachsChantEffect copy() { + return new TourachsChantEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(targetPointer.getFirst(game, source)); + Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); + if (player != null && sourcePermanent != null) { + boolean paid = false; + TargetControlledCreaturePermanent target = new TargetControlledCreaturePermanent(); + target.setNotTarget(true); + if (player.chooseUse(Outcome.Detriment, "Put a -1/-1 counter on a creature you control? (otherwise " + sourcePermanent.getLogName() + " deals 3 damage to you)", source, game) + && player.choose(Outcome.UnboostCreature, target, source.getSourceId(), game)) { + Permanent permanent = game.getPermanent(target.getFirstTarget()); + if (permanent != null) { + permanent.addCounters(CounterType.M1M1.createInstance(), source, game); + paid = true; + } + } + if (!paid) { + player.damage(3, source.getSourceId(), game, false, true); + } + return true; + } + return false; + } +} From 231bbeb88d1a04c5e720c42f2233f193ca6a7d2b Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Mon, 1 Jan 2018 18:20:09 +0100 Subject: [PATCH 18/45] Implemented cards --- Mage.Sets/src/mage/sets/FallenEmpires.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Mage.Sets/src/mage/sets/FallenEmpires.java b/Mage.Sets/src/mage/sets/FallenEmpires.java index 8aed048f3cb..fc24356ba2b 100644 --- a/Mage.Sets/src/mage/sets/FallenEmpires.java +++ b/Mage.Sets/src/mage/sets/FallenEmpires.java @@ -188,6 +188,10 @@ public class FallenEmpires extends ExpansionSet { cards.add(new SetCardInfo("Initiates of the Ebon Hand", 16, Rarity.COMMON, InitiatesOfTheEbonHand.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Initiates of the Ebon Hand", 17, Rarity.COMMON, InitiatesOfTheEbonHand.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Initiates of the Ebon Hand", 18, Rarity.COMMON, InitiatesOfTheEbonHand.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Merseine", 47, Rarity.COMMON, mage.cards.m.Merseine.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Merseine", 48, Rarity.COMMON, mage.cards.m.Merseine.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Merseine", 49, Rarity.COMMON, mage.cards.m.Merseine.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Merseine", 50, Rarity.COMMON, mage.cards.m.Merseine.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mindstab Thrull", 19, Rarity.COMMON, MindstabThrull.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mindstab Thrull", 20, Rarity.COMMON, MindstabThrull.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mindstab Thrull", 21, Rarity.COMMON, MindstabThrull.class, NON_FULL_USE_VARIOUS)); @@ -212,6 +216,7 @@ public class FallenEmpires extends ExpansionSet { cards.add(new SetCardInfo("Order of the Ebon Hand", 26, Rarity.COMMON, OrderOfTheEbonHand.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Order of the Ebon Hand", 27, Rarity.COMMON, OrderOfTheEbonHand.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Orgg", 131, Rarity.RARE, mage.cards.o.Orgg.class)); + cards.add(new SetCardInfo("Raiding Party", 132, Rarity.UNCOMMON, mage.cards.r.RaidingParty.class)); cards.add(new SetCardInfo("Rainbow Vale", 184, Rarity.RARE, mage.cards.r.RainbowVale.class)); cards.add(new SetCardInfo("Ring of Renewal", 174, Rarity.RARE, mage.cards.r.RingOfRenewal.class)); cards.add(new SetCardInfo("River Merfolk", 51, Rarity.RARE, mage.cards.r.RiverMerfolk.class)); @@ -233,6 +238,7 @@ public class FallenEmpires extends ExpansionSet { cards.add(new SetCardInfo("Thallid Devourer", 91, Rarity.UNCOMMON, mage.cards.t.ThallidDevourer.class)); cards.add(new SetCardInfo("Thelonite Druid", 92, Rarity.UNCOMMON, mage.cards.t.TheloniteDruid.class)); cards.add(new SetCardInfo("Thelonite Monk", 93, Rarity.RARE, mage.cards.t.TheloniteMonk.class)); + cards.add(new SetCardInfo("Thelon's Chant", 94, Rarity.UNCOMMON, mage.cards.t.ThelonsChant.class)); cards.add(new SetCardInfo("Thelon's Curse", 95, Rarity.RARE, mage.cards.t.ThelonsCurse.class)); cards.add(new SetCardInfo("Thorn Thallid", 96, Rarity.COMMON, ThornThallid.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Thorn Thallid", 97, Rarity.COMMON, ThornThallid.class, NON_FULL_USE_VARIOUS)); @@ -241,7 +247,11 @@ public class FallenEmpires extends ExpansionSet { cards.add(new SetCardInfo("Thrull Champion", 29, Rarity.RARE, mage.cards.t.ThrullChampion.class)); cards.add(new SetCardInfo("Thrull Retainer", 30, Rarity.UNCOMMON, mage.cards.t.ThrullRetainer.class)); cards.add(new SetCardInfo("Thrull Wizard", 31, Rarity.UNCOMMON, mage.cards.t.ThrullWizard.class)); + cards.add(new SetCardInfo("Tidal Flats", 54, Rarity.COMMON, mage.cards.t.TidalFlats.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tidal Flats", 55, Rarity.COMMON, mage.cards.t.TidalFlats.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tidal Flats", 56, Rarity.COMMON, mage.cards.t.TidalFlats.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Tidal Influence", 57, Rarity.UNCOMMON, mage.cards.t.TidalInfluence.class)); + cards.add(new SetCardInfo("Tourach's Chant", 32, Rarity.UNCOMMON, mage.cards.t.TourachsChant.class)); cards.add(new SetCardInfo("Tourach's Gate", 33, Rarity.RARE, mage.cards.t.TourachsGate.class)); cards.add(new SetCardInfo("Vodalian Knights", 58, Rarity.RARE, mage.cards.v.VodalianKnights.class)); cards.add(new SetCardInfo("Vodalian Mage", 59, Rarity.COMMON, VodalianMage.class, NON_FULL_USE_VARIOUS)); From 49aa22079fb6146ff67246d4ac1cefe84bfd253a Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Mon, 1 Jan 2018 19:09:08 +0100 Subject: [PATCH 19/45] Added net counter --- Mage/src/main/java/mage/counters/CounterType.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage/src/main/java/mage/counters/CounterType.java b/Mage/src/main/java/mage/counters/CounterType.java index 7a530dfde30..3b3f8f2f5d5 100644 --- a/Mage/src/main/java/mage/counters/CounterType.java +++ b/Mage/src/main/java/mage/counters/CounterType.java @@ -93,6 +93,7 @@ public enum CounterType { MINING("mining"), MIRE("mire"), MUSTER("muster"), + NET("net"), P0P1(new BoostCounter(0, 1).name), P1P0(new BoostCounter(1, 0).name), P1P1(new BoostCounter(1, 1).name), From 4289ad67d017ffc5b1e5777506699c94b9026fbc Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Mon, 1 Jan 2018 19:10:02 +0100 Subject: [PATCH 20/45] Updated Merseine --- Mage.Sets/src/mage/cards/m/Merseine.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Mage.Sets/src/mage/cards/m/Merseine.java b/Mage.Sets/src/mage/cards/m/Merseine.java index dca22e5da9d..f9a7ffb438c 100644 --- a/Mage.Sets/src/mage/cards/m/Merseine.java +++ b/Mage.Sets/src/mage/cards/m/Merseine.java @@ -69,13 +69,13 @@ public class Merseine extends CardImpl { this.addAbility(new EnchantAbility(auraTarget.getTargetName())); // Merseine enters the battlefield with three net counters on it. - Effect effect = new AddCountersSourceEffect(CounterType.ECHO.createInstance(3)); + Effect effect = new AddCountersSourceEffect(CounterType.NET.createInstance(3)); effect.setText("with three net counters on it"); this.addAbility(new EntersBattlefieldAbility(effect)); // Enchanted creature doesn't untap during its controller's untap step if Merseine has a net counter on it. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousRuleModifyingEffect(new DontUntapInControllersUntapStepEnchantedEffect(), - new SourceHasCounterCondition(CounterType.ECHO)).setText("Enchanted creature doesn't untap during its controller's untap step if Merseine has a net counter on it"))); + new SourceHasCounterCondition(CounterType.NET)).setText("Enchanted creature doesn't untap during its controller's untap step if Merseine has a net counter on it"))); // Pay enchanted creature's mana cost: Remove a net counter from Merseine. Any player may activate this ability, but only if he or she controls the enchanted creature. SimpleActivatedAbility ability = new MerseineActivatedAbility(); @@ -96,7 +96,7 @@ public class Merseine extends CardImpl { class MerseineActivatedAbility extends SimpleActivatedAbility { public MerseineActivatedAbility() { - super(Zone.BATTLEFIELD, new RemoveCounterSourceEffect(CounterType.ECHO.createInstance()), new MerseineCost()); + super(Zone.BATTLEFIELD, new RemoveCounterSourceEffect(CounterType.NET.createInstance()), new MerseineCost()); } private MerseineActivatedAbility(final MerseineActivatedAbility ability) { From 3cd7ea7752507efbbfa985b160cded3bc1d3066e Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Mon, 1 Jan 2018 19:44:02 +0100 Subject: [PATCH 21/45] Added player announcements to Raiding Party --- Mage.Sets/src/mage/cards/r/RaidingParty.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Mage.Sets/src/mage/cards/r/RaidingParty.java b/Mage.Sets/src/mage/cards/r/RaidingParty.java index 2db0dfd4c10..0552f432078 100644 --- a/Mage.Sets/src/mage/cards/r/RaidingParty.java +++ b/Mage.Sets/src/mage/cards/r/RaidingParty.java @@ -143,6 +143,10 @@ class RaidingPartyEffect extends OneShotEffect { if (player.choose(Outcome.Benefit, plainsToSaveTarget, source.getSourceId(), game)) { for (UUID plainsId : plainsToSaveTarget.getTargets()) { plainsToSave.add(plainsId); + Permanent plains = game.getPermanent(plainsId); + if (plains != null) { + game.informPlayers(player.getLogName() + " chose " + plains.getLogName() + " to not be destroyed by " + sourcePermanent.getLogName()); + } } } } From c1bf846973f797a995961399a491e7a25e3e7ddf Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Tue, 2 Jan 2018 00:14:29 +0400 Subject: [PATCH 22/45] UI: added new memory statistics, warnings and hints (see #4324) --- .../mage/client/util/stats/MemoryStats.java | 52 ++++++++++++++++++ .../util/stats/MemoryUsageStatUtil.java | 25 --------- .../client/util/stats/UpdateMemUsageTask.java | 55 ++++++++++++++++--- 3 files changed, 99 insertions(+), 33 deletions(-) create mode 100644 Mage.Client/src/main/java/mage/client/util/stats/MemoryStats.java delete mode 100644 Mage.Client/src/main/java/mage/client/util/stats/MemoryUsageStatUtil.java diff --git a/Mage.Client/src/main/java/mage/client/util/stats/MemoryStats.java b/Mage.Client/src/main/java/mage/client/util/stats/MemoryStats.java new file mode 100644 index 00000000000..dfa53da2734 --- /dev/null +++ b/Mage.Client/src/main/java/mage/client/util/stats/MemoryStats.java @@ -0,0 +1,52 @@ +package mage.client.util.stats; + +/** + * + * @author JayDi85 + */ +public class MemoryStats { + + private float Available = 0; + private float MaxAvailable = 0; + private float Used = 0; + private float Free = 0; + + public MemoryStats(float MaxAvailable, float Available, float Used, float Free){ + this.setMaxAvailable(MaxAvailable); + this.setAvailable(Available); + this.setUsed(Used); + this.setFree(Free); + } + + public float getAvailable() { + return Available; + } + + public void setAvailable(float available) { + Available = available; + } + + public float getUsed() { + return Used; + } + + public void setUsed(float used) { + Used = used; + } + + public float getFree() { + return Free; + } + + public void setFree(float free) { + Free = free; + } + + public float getMaxAvailable() { + return MaxAvailable; + } + + public void setMaxAvailable(float maxAvailable) { + MaxAvailable = maxAvailable; + } +} \ No newline at end of file diff --git a/Mage.Client/src/main/java/mage/client/util/stats/MemoryUsageStatUtil.java b/Mage.Client/src/main/java/mage/client/util/stats/MemoryUsageStatUtil.java deleted file mode 100644 index 900b284fa9e..00000000000 --- a/Mage.Client/src/main/java/mage/client/util/stats/MemoryUsageStatUtil.java +++ /dev/null @@ -1,25 +0,0 @@ -package mage.client.util.stats; - -/** - * @author noxx - */ -public final class MemoryUsageStatUtil { - - private MemoryUsageStatUtil() {} - - /** - * Returns percentage of available memory used at runtime. - * If not possible to determine, returns -1. - * - * @return - */ - public static float getMemoryFreeStatPercentage() { - Runtime runtime = Runtime.getRuntime(); - if (runtime.maxMemory() != 0) { - long usedMem = runtime.totalMemory() - runtime.freeMemory(); - return (1 - (1.0f*usedMem)/runtime.maxMemory())*100; - } else { - return -1; - } - } -} diff --git a/Mage.Client/src/main/java/mage/client/util/stats/UpdateMemUsageTask.java b/Mage.Client/src/main/java/mage/client/util/stats/UpdateMemUsageTask.java index 61b28bda4ed..31d62d24962 100644 --- a/Mage.Client/src/main/java/mage/client/util/stats/UpdateMemUsageTask.java +++ b/Mage.Client/src/main/java/mage/client/util/stats/UpdateMemUsageTask.java @@ -1,5 +1,6 @@ package mage.client.util.stats; +import java.awt.*; import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; @@ -11,11 +12,13 @@ import org.apache.log4j.Logger; * This updates the mem usage info in the Mage client every * MEM_USAGE_UPDATE_TIME ms. * - * @author noxx + * @author noxx, JayDi85 */ -public class UpdateMemUsageTask extends SwingWorker { + +public class UpdateMemUsageTask extends SwingWorker { private static final int MEM_USAGE_UPDATE_TIME = 2000; + private static final int MEM_USAGE_WARNING_PERCENT = 80; // red color for mem used more than xx% private final JLabel jLabelToDisplayInfo; @@ -23,24 +26,60 @@ public class UpdateMemUsageTask extends SwingWorker { public UpdateMemUsageTask(JLabel jLabelToDisplayInfo) { this.jLabelToDisplayInfo = jLabelToDisplayInfo; + this.jLabelToDisplayInfo.setToolTipText("Memory usage statistics"); } @Override protected Void doInBackground() throws Exception { while (!isCancelled()) { - float memUsage = MemoryUsageStatUtil.getMemoryFreeStatPercentage(); - this.publish(memUsage >= 0 ? memUsage : null); + MemoryStats memoryStats = new MemoryStats(0, 0, 0, 0); + + Runtime runtime = Runtime.getRuntime(); + if (runtime.maxMemory() != 0) { + memoryStats.setMaxAvailable(runtime.maxMemory()); + memoryStats.setAvailable(runtime.totalMemory()); + memoryStats.setFree(runtime.freeMemory()); + memoryStats.setUsed(runtime.totalMemory() - runtime.freeMemory()); + } + + this.publish(memoryStats); TimeUnit.MILLISECONDS.sleep(MEM_USAGE_UPDATE_TIME); } return null; } @Override - protected void process(List chunks) { + protected void process(List chunks) { if (chunks != null && !chunks.isEmpty()) { - Float memUsage = chunks.get(chunks.size() - 1); - if (memUsage != null) { - jLabelToDisplayInfo.setText(Math.round(memUsage) + "% Mem free"); + MemoryStats memoryStats = chunks.get(chunks.size() - 1); + if (memoryStats != null) { + int max = Math.round(memoryStats.getMaxAvailable() / (1000 * 1000)); + int used = Math.round(memoryStats.getUsed() / (1000 * 1000)); + int total = Math.round(memoryStats.getAvailable() / (1000 * 1000)); + int percent = 0; + if(max != 0){ + percent = Math.round((used * 100) / max); + } + + jLabelToDisplayInfo.setText("Memory used: " + percent + "% (" + used + " of " + max + " MB)"); + String warning = ""; + String optimizeHint = "

If you see low memory warning and have free system memory then try to increase max limit in launcher settings:
" + + " - Go to launcher -> settings -> java tab;
" + + " - Find client java options (it's may contain many commands);
" + + " - Find max available memory setting: -Xmx256m (it's must start with -Xmx);
" + + " - Increase number in that value from 256 to 512, or 512 to 1024;
" + + " - Save new settings and restart application."; + if(percent >= MEM_USAGE_WARNING_PERCENT){ + jLabelToDisplayInfo.setForeground(Color.red); + warning = "

WARNING
" + + "Application memory limit almost reached. Errors and freezes are very possible."; + + }else{ + jLabelToDisplayInfo.setForeground(Color.black); + } + + this.jLabelToDisplayInfo.setToolTipText("Memory usage statistics" + warning + optimizeHint); + return; } } From 9009d72c823612ee2149d5a1aa4bb10ed070aa0b Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Tue, 2 Jan 2018 00:15:44 +0400 Subject: [PATCH 23/45] UI: increased tooltip time before hide (60 secs) --- Mage.Client/src/main/java/mage/client/MageFrame.java | 6 ++++-- .../src/main/java/mage/client/constants/Constants.java | 3 +++ .../src/main/java/mage/client/game/HelperPanel.java | 7 ++++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/MageFrame.java b/Mage.Client/src/main/java/mage/client/MageFrame.java index 9c5d93d6fe4..a15e4ba4f8f 100644 --- a/Mage.Client/src/main/java/mage/client/MageFrame.java +++ b/Mage.Client/src/main/java/mage/client/MageFrame.java @@ -50,6 +50,7 @@ import mage.client.chat.ChatPanelBasic; import mage.client.components.*; import mage.client.components.ext.dlg.DialogManager; import mage.client.components.tray.MageTray; +import mage.client.constants.Constants; import mage.client.constants.Constants.DeckEditorMode; import mage.client.deckeditor.DeckEditorPane; import mage.client.deckeditor.collection.viewer.CollectionViewerPane; @@ -92,8 +93,6 @@ import org.mage.plugins.card.images.DownloadPictures; import org.mage.plugins.card.info.CardInfoPaneImpl; import org.mage.plugins.card.utils.impl.ImageManagerImpl; -import static org.mage.plugins.card.utils.CardImageUtils.getImagesDir; - /** * @author BetaSteward_at_googlemail.com */ @@ -285,6 +284,9 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { balloonTip.setPositioner(new LeftAbovePositioner(0, 0)); balloonTip.setVisible(false); + // tooltips delay in ms + ToolTipManager.sharedInstance().setDismissDelay(Constants.TOOLTIPS_DELAY_MS); + mageToolbar.add(createSwitchPanelsButton(), 0); mageToolbar.add(new javax.swing.JToolBar.Separator(), 1); diff --git a/Mage.Client/src/main/java/mage/client/constants/Constants.java b/Mage.Client/src/main/java/mage/client/constants/Constants.java index b1de743dc28..5547517a859 100644 --- a/Mage.Client/src/main/java/mage/client/constants/Constants.java +++ b/Mage.Client/src/main/java/mage/client/constants/Constants.java @@ -69,6 +69,9 @@ public final class Constants { public static final int POWBOX_TEXT_MAX_LEFT = 212; public static final int DAMAGE_MAX_LEFT = 180; + // tooltip hints delay in ms (need more time to display long hints withour hiding) + public static final int TOOLTIPS_DELAY_MS = 60 * 1000; + public static final Border EMPTY_BORDER = BorderFactory.createEmptyBorder(2, 2, 2, 2); public static final double SCALE_FACTOR = 0.5; diff --git a/Mage.Client/src/main/java/mage/client/game/HelperPanel.java b/Mage.Client/src/main/java/mage/client/game/HelperPanel.java index d801a15d3a9..15bc6528665 100644 --- a/Mage.Client/src/main/java/mage/client/game/HelperPanel.java +++ b/Mage.Client/src/main/java/mage/client/game/HelperPanel.java @@ -51,7 +51,9 @@ import javax.swing.UIManager; import mage.client.SessionHandler; import mage.client.components.MageTextArea; +import mage.client.constants.Constants; import mage.client.game.FeedbackPanel.FeedbackMode; + import static mage.client.game.FeedbackPanel.FeedbackMode.QUESTION; import mage.client.util.GUISizeHelper; import static mage.constants.PlayerAction.REQUEST_AUTO_ANSWER_ID_NO; @@ -82,7 +84,6 @@ public class HelperPanel extends JPanel { private javax.swing.JButton linkSpecial; private javax.swing.JButton linkUndo; - private final int defaultDismissTimeout = ToolTipManager.sharedInstance().getDismissDelay(); private final Object tooltipBackground = UIManager.get("info"); private static final String CMD_AUTO_ANSWER_ID_YES = "cmdAutoAnswerIdYes"; @@ -232,13 +233,13 @@ public class HelperPanel extends JPanel { @Override public void mouseEntered(MouseEvent me) { - ToolTipManager.sharedInstance().setDismissDelay(100000); + ToolTipManager.sharedInstance().setDismissDelay(100 * 1000); UIManager.put("info", Color.DARK_GRAY); } @Override public void mouseExited(MouseEvent me) { - ToolTipManager.sharedInstance().setDismissDelay(defaultDismissTimeout); + ToolTipManager.sharedInstance().setDismissDelay(Constants.TOOLTIPS_DELAY_MS); UIManager.put("info", tooltipBackground); } }); From b876b011a5aee78cb4bcef6daea148979e69b6ef Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Tue, 2 Jan 2018 00:41:03 +0100 Subject: [PATCH 24/45] (Re)added missing ability --- Mage.Sets/src/mage/cards/d/DaughterOfAutumn.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/cards/d/DaughterOfAutumn.java b/Mage.Sets/src/mage/cards/d/DaughterOfAutumn.java index 19cb73f7745..ad1ea1a443b 100644 --- a/Mage.Sets/src/mage/cards/d/DaughterOfAutumn.java +++ b/Mage.Sets/src/mage/cards/d/DaughterOfAutumn.java @@ -72,6 +72,7 @@ public class DaughterOfAutumn extends CardImpl { // {W}: The next 1 damage that would be dealt to target white creature this turn is dealt to Daughter of Autumn instead. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DaughterOfAutumnPreventDamageTargetEffect(Duration.EndOfTurn, 1), new ManaCostsImpl("{W}")); ability.addTarget(new TargetCreaturePermanent(filter)); + this.addAbility(ability); } public DaughterOfAutumn(final DaughterOfAutumn card) { From a6774cdc740c78ceb79bfedf7e24c04711561076 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Tue, 2 Jan 2018 05:45:16 +0100 Subject: [PATCH 25/45] Implemented Apocalypse Chime --- .../src/mage/cards/a/ApocalypseChime.java | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/a/ApocalypseChime.java diff --git a/Mage.Sets/src/mage/cards/a/ApocalypseChime.java b/Mage.Sets/src/mage/cards/a/ApocalypseChime.java new file mode 100644 index 00000000000..8f628925461 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/ApocalypseChime.java @@ -0,0 +1,79 @@ +/* + * 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.a; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DestroyAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.other.ExpansionSetPredicate; +import mage.filter.predicate.permanent.TokenPredicate; + +/** + * + * @author L_J + */ +public class ApocalypseChime extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("nontoken permanents with a name originally printed in the Homelands expansion"); + + static { + filter.add(Predicates.and( + Predicates.not(new TokenPredicate()), + new ExpansionSetPredicate("HML") + )); + } + + public ApocalypseChime(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{2}"); + + // {2}, {T}, Sacrifice Apocalypse Chime: Destroy all nontoken permanents with a name originally printed in the Homelands expansion. They can't be regenerated. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DestroyAllEffect(filter, true), new ManaCostsImpl("{2}")); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeSourceCost()); + this.addAbility(ability); + } + + public ApocalypseChime(final ApocalypseChime card) { + super(card); + } + + @Override + public ApocalypseChime copy() { + return new ApocalypseChime(this); + } +} From aa10e2d3365e4d4f1c31117c7179a925995167b0 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Tue, 2 Jan 2018 05:46:33 +0100 Subject: [PATCH 26/45] Implemented Autumn Willow --- Mage/src/main/java/mage/constants/AsThoughEffectType.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage/src/main/java/mage/constants/AsThoughEffectType.java b/Mage/src/main/java/mage/constants/AsThoughEffectType.java index af2e4bf30f6..50f7caeb4f8 100644 --- a/Mage/src/main/java/mage/constants/AsThoughEffectType.java +++ b/Mage/src/main/java/mage/constants/AsThoughEffectType.java @@ -17,6 +17,7 @@ public enum AsThoughEffectType { CAST_AS_INSTANT, ACTIVATE_AS_INSTANT, DAMAGE, + SHROUD, HEXPROOF, PAY, LOOK_AT_FACE_DOWN, From bde116e201cc4f31d76bdd91a171924c03a868c2 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Tue, 2 Jan 2018 05:48:06 +0100 Subject: [PATCH 27/45] Implemented Autumn Willow --- Mage/src/main/java/mage/game/permanent/PermanentImpl.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java index 95c27b3c634..3859322d336 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java @@ -946,7 +946,9 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { public boolean canBeTargetedBy(MageObject source, UUID sourceControllerId, Game game) { if (source != null) { if (abilities.containsKey(ShroudAbility.getInstance().getId())) { - return false; + if (!game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.SHROUD, sourceControllerId, game)) { + return false; + } } if (abilities.containsKey(HexproofAbility.getInstance().getId())) { if (game.getPlayer(this.getControllerId()).hasOpponent(sourceControllerId, game) From 404c79efb1ab3a9bb7df684af88c2a27b30b7c93 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Tue, 2 Jan 2018 05:48:49 +0100 Subject: [PATCH 28/45] Implemented Autumn Willow --- Mage.Sets/src/mage/cards/a/AutumnWillow.java | 118 +++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/a/AutumnWillow.java diff --git a/Mage.Sets/src/mage/cards/a/AutumnWillow.java b/Mage.Sets/src/mage/cards/a/AutumnWillow.java new file mode 100644 index 00000000000..3a090ba7ac8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AutumnWillow.java @@ -0,0 +1,118 @@ +/* + * 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.a; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.keyword.ShroudAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPlayer; + +/** + * Gatecrash FAQ 21.01.2013 + * + * Creatures your opponents control don't actually lose hexproof, although you + * will ignore hexproof for purposes of choosing targets of spells and abilities + * you control. + * + * Creatures that come under your control after Glaring Spotlight's last ability + * resolves won't have hexproof but can't be blocked that turn. + * + * @author L_J + */ +public class AutumnWillow extends CardImpl { + + public AutumnWillow(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}{G}"); + addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.AVATAR); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Shroud + this.addAbility(ShroudAbility.getInstance()); + + // {G}: Until end of turn, Autumn Willow can be the target of spells and abilities controlled by target player as though it didn't have shroud. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new AutumnWillowEffect(), new ManaCostsImpl("{G}")); + ability.addTarget(new TargetPlayer()); + this.addAbility(ability); + } + + public AutumnWillow(final AutumnWillow card) { + super(card); + } + + @Override + public AutumnWillow copy() { + return new AutumnWillow(this); + } +} + +class AutumnWillowEffect extends AsThoughEffectImpl { + + public AutumnWillowEffect() { + super(AsThoughEffectType.SHROUD, Duration.EndOfTurn, Outcome.Benefit); + staticText = "Until end of turn, Autumn Willow can be the target of spells and abilities controlled by target player as though it didn't have shroud"; + } + + public AutumnWillowEffect(final AutumnWillowEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public AutumnWillowEffect copy() { + return new AutumnWillowEffect(this); + } + + @Override + public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) { + if (affectedControllerId.equals(source.getFirstTarget())) { + Permanent creature = game.getPermanent(sourceId); + if (creature != null) { + if (sourceId == source.getSourceId()) { + return true; + } + } + } + return false; + } +} From 5681c945a9fbeb7c521d74efb06f4bab8298f376 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Tue, 2 Jan 2018 05:50:41 +0100 Subject: [PATCH 29/45] Removed comment --- Mage.Sets/src/mage/cards/a/AutumnWillow.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Mage.Sets/src/mage/cards/a/AutumnWillow.java b/Mage.Sets/src/mage/cards/a/AutumnWillow.java index 3a090ba7ac8..16829e0c7d9 100644 --- a/Mage.Sets/src/mage/cards/a/AutumnWillow.java +++ b/Mage.Sets/src/mage/cards/a/AutumnWillow.java @@ -43,14 +43,6 @@ import mage.game.permanent.Permanent; import mage.target.TargetPlayer; /** - * Gatecrash FAQ 21.01.2013 - * - * Creatures your opponents control don't actually lose hexproof, although you - * will ignore hexproof for purposes of choosing targets of spells and abilities - * you control. - * - * Creatures that come under your control after Glaring Spotlight's last ability - * resolves won't have hexproof but can't be blocked that turn. * * @author L_J */ From 497ebebc05c8cc4e74093f6c2df5849b6d68d4d7 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Tue, 2 Jan 2018 05:52:47 +0100 Subject: [PATCH 30/45] Implemented Broken Visage --- .../token/BrokenVisageSpiritToken.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 Mage/src/main/java/mage/game/permanent/token/BrokenVisageSpiritToken.java diff --git a/Mage/src/main/java/mage/game/permanent/token/BrokenVisageSpiritToken.java b/Mage/src/main/java/mage/game/permanent/token/BrokenVisageSpiritToken.java new file mode 100644 index 00000000000..9108b920c50 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/BrokenVisageSpiritToken.java @@ -0,0 +1,52 @@ +/* +* 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.game.permanent.token; + +import mage.constants.CardType; +import mage.constants.SubType; +import mage.MageInt; + +/** + * + * @author spjspj & L_J + */ +public class BrokenVisageSpiritToken extends Token { + + public BrokenVisageSpiritToken() { + this(0,0); + } + + public BrokenVisageSpiritToken(int tokenPower, int tokenToughness) { + super("Spirit", new StringBuilder(tokenPower).append('/').append(tokenToughness).append(" black Spirit creature token").toString()); + cardType.add(CardType.CREATURE); + color.setBlack(true); + subtype.add(SubType.SPIRIT); + power = new MageInt(tokenPower); + toughness = new MageInt(tokenToughness); + } +} From 82d4ddd0fa9336918678316e6d54754c161f3d99 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Tue, 2 Jan 2018 05:53:38 +0100 Subject: [PATCH 31/45] Broken Visage --- Mage.Sets/src/mage/cards/b/BrokenVisage.java | 117 +++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/b/BrokenVisage.java diff --git a/Mage.Sets/src/mage/cards/b/BrokenVisage.java b/Mage.Sets/src/mage/cards/b/BrokenVisage.java new file mode 100644 index 00000000000..9b9651b0b8d --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BrokenVisage.java @@ -0,0 +1,117 @@ +/* + * 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.b; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.SacrificeTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.filter.predicate.permanent.AttackingPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.BrokenVisageSpiritToken; +import mage.target.common.TargetCreaturePermanent; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author L_J + */ +public class BrokenVisage extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("nonartifact attacking creature"); + static { + filter.add(Predicates.not(new CardTypePredicate(CardType.ARTIFACT))); + filter.add(new AttackingPredicate()); + } + + public BrokenVisage(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{4}{B}"); + + // Destroy target nonartifact attacking creature. It can't be regenerated. Create a black Spirit creature token. Its power is equal to that creature's power and its toughness is equal to that creature's toughness. Sacrifice the token at the beginning of the next end step. + this.getSpellAbility().addEffect(new BrokenVisageEffect()); + this.getSpellAbility().addTarget(new TargetCreaturePermanent(filter)); + } + + public BrokenVisage(final BrokenVisage card) { + super(card); + } + + @Override + public BrokenVisage copy() { + return new BrokenVisage(this); + } +} + +class BrokenVisageEffect extends OneShotEffect { + + public BrokenVisageEffect() { + super(Outcome.DestroyPermanent); + this.staticText = "Destroy target nonartifact attacking creature. It can't be regenerated. Create a black Spirit creature token. Its power is equal to that creature's power and its toughness is equal to that creature's toughness. Sacrifice the token at the beginning of the next end step"; + } + + public BrokenVisageEffect(final BrokenVisageEffect effect) { + super(effect); + } + + @Override + public BrokenVisageEffect copy() { + return new BrokenVisageEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanentOrLKIBattlefield(this.getTargetPointer().getFirst(game, source)); + if (permanent != null) { + permanent.destroy(source.getSourceId(), game, true); + CreateTokenEffect effect = new CreateTokenEffect(new BrokenVisageSpiritToken(permanent.getPower().getValue(), permanent.getToughness().getValue())); + effect.apply(game, source); + for (UUID tokenId : effect.getLastAddedTokenIds()) { + Permanent tokenPermanent = game.getPermanent(tokenId); + if (tokenPermanent != null) { + SacrificeTargetEffect sacrificeEffect = new SacrificeTargetEffect("Sacrifice the token at the beginning of the next end step", source.getControllerId()); + sacrificeEffect.setTargetPointer(new FixedTarget(tokenPermanent, game)); + DelayedTriggeredAbility delayedAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(sacrificeEffect); + game.addDelayedTriggeredAbility(delayedAbility, source); + } + } + } + return true; + } + +} From ea29eb5a85457b9d6228241a4db2b05f9e0b128d Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Tue, 2 Jan 2018 05:54:29 +0100 Subject: [PATCH 32/45] Hazduhr the Abbot --- .../src/mage/cards/h/HazduhrTheAbbot.java | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/h/HazduhrTheAbbot.java diff --git a/Mage.Sets/src/mage/cards/h/HazduhrTheAbbot.java b/Mage.Sets/src/mage/cards/h/HazduhrTheAbbot.java new file mode 100644 index 00000000000..bbe55f549fc --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HazduhrTheAbbot.java @@ -0,0 +1,130 @@ +/* + * 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.h; + +import java.util.UUID; +import mage.MageInt; +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.RedirectionEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Duration; +import mage.constants.SuperType; +import mage.constants.Zone; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.ColorPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; +import mage.target.common.TargetControlledCreaturePermanent; + +/** + * + * @author TheElk801 & L_J + */ +public class HazduhrTheAbbot extends CardImpl { + + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("white creature you control"); + + static { + filter.add(new ColorPredicate(ObjectColor.WHITE)); + } + + public HazduhrTheAbbot(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}{W}"); + addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CLERIC); + this.power = new MageInt(2); + this.toughness = new MageInt(5); + + // {X}, {T}: The next X damage that would be dealt this turn to target white creature you control is dealt to Hazduhr the Abbot instead. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new HazduhrTheAbbotRedirectDamageEffect(Duration.EndOfTurn), new ManaCostsImpl("{X}")); + ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetControlledCreaturePermanent(filter)); + this.addAbility(ability); + } + + public HazduhrTheAbbot(final HazduhrTheAbbot card) { + super(card); + } + + @Override + public HazduhrTheAbbot copy() { + return new HazduhrTheAbbot(this); + } +} + +class HazduhrTheAbbotRedirectDamageEffect extends RedirectionEffect { + + private static FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(); + + public HazduhrTheAbbotRedirectDamageEffect(Duration duration) { + super(duration, 0, true); + this.staticText = "The next X damage that would be dealt this turn to target white creature you control is dealt to {this} instead"; + } + + public HazduhrTheAbbotRedirectDamageEffect(final HazduhrTheAbbotRedirectDamageEffect effect) { + super(effect); + } + + @Override + public void init(Ability source, Game game) { + amountToRedirect = source.getManaCostsToPay().getX(); + } + + @Override + public HazduhrTheAbbotRedirectDamageEffect copy() { + return new HazduhrTheAbbotRedirectDamageEffect(this); + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + Permanent permanent = game.getBattlefield().getPermanent(source.getSourceId()); + if (permanent != null) { + if (filter.match(permanent, permanent.getId(), permanent.getControllerId(), game)) { + if (event.getTargetId().equals(getTargetPointer().getFirst(game, source))) { + if (event.getTargetId() != null) { + TargetPermanent target = new TargetPermanent(); + target.add(source.getSourceId(), game); + redirectTarget = target; + return true; + } + } + } + } + return false; + } +} From 98cab3a0b72d8a5ff9893ac46004dd3610424128 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Tue, 2 Jan 2018 05:55:32 +0100 Subject: [PATCH 33/45] Implemented Joven's Tools --- Mage.Sets/src/mage/cards/j/JovensTools.java | 76 +++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/j/JovensTools.java diff --git a/Mage.Sets/src/mage/cards/j/JovensTools.java b/Mage.Sets/src/mage/cards/j/JovensTools.java new file mode 100644 index 00000000000..642ece28ae4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/j/JovensTools.java @@ -0,0 +1,76 @@ +/* + * 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.j; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.combat.CantBeBlockedByAllTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Duration; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author L_J + */ +public class JovensTools extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("except by walls"); + static { + filter.add(Predicates.not(new SubtypePredicate(SubType.WALL))); + } + + public JovensTools(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{6}"); + + // {4}, {T}: Target creature can't be blocked this turn except by Walls. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new CantBeBlockedByAllTargetEffect(filter, Duration.EndOfTurn), new GenericManaCost(4)); + ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability); + } + + public JovensTools(final JovensTools card) { + super(card); + } + + @Override + public JovensTools copy() { + return new JovensTools(this); + } +} From bc3e647544c98ee8569d2e6af4523eade001b111 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Tue, 2 Jan 2018 05:56:13 +0100 Subject: [PATCH 34/45] Implemented Mammoth Harness --- .../src/mage/cards/m/MammothHarness.java | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/m/MammothHarness.java diff --git a/Mage.Sets/src/mage/cards/m/MammothHarness.java b/Mage.Sets/src/mage/cards/m/MammothHarness.java new file mode 100644 index 00000000000..d17c63e4d9d --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MammothHarness.java @@ -0,0 +1,135 @@ +/* + * 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.m; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.BlocksOrBecomesBlockedTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.continuous.LoseAbilityAttachedEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author L_J + */ +public class MammothHarness extends CardImpl { + + public MammothHarness(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{3}{G}"); + this.subtype.add(SubType.AURA); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.LoseAbility)); + Ability ability = new EnchantAbility(auraTarget.getTargetName()); + this.addAbility(ability); + + // Enchanted creature loses flying. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new LoseAbilityAttachedEffect(FlyingAbility.getInstance(), AttachmentType.AURA))); + + // Whenever enchanted creature blocks or becomes blocked by a creature, the other creature gains first strike until end of turn. + this.addAbility(new MammothHarnessTriggeredAbility()); + } + + public MammothHarness(final MammothHarness card) { + super(card); + } + + @Override + public MammothHarness copy() { + return new MammothHarness(this); + } +} + +class MammothHarnessTriggeredAbility extends BlocksOrBecomesBlockedTriggeredAbility { + + public MammothHarnessTriggeredAbility() { + super(new GainAbilityTargetEffect(FirstStrikeAbility.getInstance(), Duration.EndOfTurn), StaticFilters.FILTER_PERMANENT_CREATURE, false, null, false); + } + + public MammothHarnessTriggeredAbility(final MammothHarnessTriggeredAbility ability) { + super(ability); + + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(this.getSourceId()); + if (sourcePermanent != null) { + Permanent attachedTo = game.getPermanentOrLKIBattlefield(sourcePermanent.getAttachedTo()); + if (sourcePermanent != null) { + if (event.getSourceId().equals(attachedTo.getId())) { + Permanent blocked = game.getPermanent(event.getTargetId()); + if (blocked != null && filter.match(blocked, game)) { + this.getEffects().setTargetPointer(new FixedTarget(event.getTargetId())); + return true; + } + } + if (event.getTargetId().equals(attachedTo.getId())) { + Permanent blocker = game.getPermanent(event.getSourceId()); + if (blocker != null) { + this.getEffects().setTargetPointer(new FixedTarget(event.getSourceId())); + return true; + } + } + } + } + return false; + } + + @Override + public String getRule() { + return " Whenever enchanted creature blocks or becomes blocked by a creature, the other creature gains first strike until end of turn."; + } + + @Override + public MammothHarnessTriggeredAbility copy() { + return new MammothHarnessTriggeredAbility(this); + } +} From a9a2f85c4c095e5a79159dc3841d7d6681a9c855 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Tue, 2 Jan 2018 05:57:10 +0100 Subject: [PATCH 35/45] Implemented Rashka the Slayer --- .../src/mage/cards/r/RashkaTheSlayer.java | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/r/RashkaTheSlayer.java diff --git a/Mage.Sets/src/mage/cards/r/RashkaTheSlayer.java b/Mage.Sets/src/mage/cards/r/RashkaTheSlayer.java new file mode 100644 index 00000000000..e9d4a65cdea --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RashkaTheSlayer.java @@ -0,0 +1,127 @@ +/* + * 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.ObjectColor; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.keyword.ReachAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Duration; +import mage.constants.SuperType; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.ColorPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; + +/** + * + * @author L_J + */ +public class RashkaTheSlayer extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("black creature"); + + static { + filter.add(new ColorPredicate(ObjectColor.BLACK)); + } + + public RashkaTheSlayer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}{W}"); + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ARCHER); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // Whenever Rashka the Slayer blocks one or more black creatures, Rashka gets +1/+2 until end of turn. + this.addAbility(new RashkaTheSlayerTriggeredAbility(new BoostSourceEffect(1, 2, Duration.EndOfTurn), filter, false)); + } + + public RashkaTheSlayer(final RashkaTheSlayer card) { + super(card); + } + + @Override + public RashkaTheSlayer copy() { + return new RashkaTheSlayer(this); + } +} + +class RashkaTheSlayerTriggeredAbility extends TriggeredAbilityImpl { + + protected FilterPermanent filter; + + public RashkaTheSlayerTriggeredAbility(Effect effect, FilterPermanent filter, boolean optional) { + super(Zone.BATTLEFIELD, effect, optional); + this.filter = filter; + } + + public RashkaTheSlayerTriggeredAbility(final RashkaTheSlayerTriggeredAbility ability) { + super(ability); + this.filter = ability.filter; + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.BLOCKER_DECLARED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getSourceId().equals(this.getSourceId())) { + Permanent blocked = game.getPermanent(event.getTargetId()); + if (blocked != null && filter.match(blocked, game)) { + return true; + } + } + return false; + } + + @Override + public String getRule() { + return "Whenever {this} blocks a " + filter.getMessage() + ", " + super.getRule(); + } + + @Override + public RashkaTheSlayerTriggeredAbility copy() { + return new RashkaTheSlayerTriggeredAbility(this); + } +} From ff92c0c78a1aa1bc13b0766268ec48186474c189 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Tue, 2 Jan 2018 05:58:05 +0100 Subject: [PATCH 36/45] Implemented cards --- Mage.Sets/src/mage/sets/Homelands.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Mage.Sets/src/mage/sets/Homelands.java b/Mage.Sets/src/mage/sets/Homelands.java index 6113b95dca7..2a5cb439a87 100644 --- a/Mage.Sets/src/mage/sets/Homelands.java +++ b/Mage.Sets/src/mage/sets/Homelands.java @@ -83,6 +83,8 @@ public class Homelands extends ExpansionSet { cards.add(new SetCardInfo("An-Havva Inn", 52, Rarity.UNCOMMON, mage.cards.a.AnHavvaInn.class)); cards.add(new SetCardInfo("An-Havva Township", 136, Rarity.UNCOMMON, mage.cards.a.AnHavvaTownship.class)); cards.add(new SetCardInfo("An-Zerrin Ruins", 87, Rarity.RARE, mage.cards.a.AnZerrinRuins.class)); + cards.add(new SetCardInfo("Apocalypse Chime", 126, Rarity.RARE, mage.cards.a.ApocalypseChime.class)); + cards.add(new SetCardInfo("Autumn Willow", 53, Rarity.RARE, mage.cards.a.AutumnWillow.class)); cards.add(new SetCardInfo("Aysen Abbey", 137, Rarity.UNCOMMON, mage.cards.a.AysenAbbey.class)); cards.add(new SetCardInfo("Aysen Bureaucrats", 104, Rarity.COMMON, mage.cards.a.AysenBureaucrats.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Aysen Bureaucrats", 105, Rarity.COMMON, mage.cards.a.AysenBureaucrats.class, NON_FULL_USE_VARIOUS)); @@ -91,6 +93,7 @@ public class Homelands extends ExpansionSet { cards.add(new SetCardInfo("Baki's Curse", 27, Rarity.RARE, mage.cards.b.BakisCurse.class)); cards.add(new SetCardInfo("Baron Sengir", 1, Rarity.RARE, mage.cards.b.BaronSengir.class)); cards.add(new SetCardInfo("Black Carriage", 2, Rarity.RARE, mage.cards.b.BlackCarriage.class)); + cards.add(new SetCardInfo("Broken Visage", 3, Rarity.RARE, mage.cards.b.BrokenVisage.class)); cards.add(new SetCardInfo("Carapace", 54, Rarity.COMMON, mage.cards.c.Carapace.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Carapace", 55, Rarity.COMMON, mage.cards.c.Carapace.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Castle Sengir", 138, Rarity.UNCOMMON, mage.cards.c.CastleSengir.class)); @@ -124,6 +127,7 @@ public class Homelands extends ExpansionSet { cards.add(new SetCardInfo("Forget", 32, Rarity.RARE, mage.cards.f.Forget.class)); cards.add(new SetCardInfo("Ghost Hounds", 12, Rarity.UNCOMMON, mage.cards.g.GhostHounds.class)); cards.add(new SetCardInfo("Grandmother Sengir", 13, Rarity.RARE, mage.cards.g.GrandmotherSengir.class)); + cards.add(new SetCardInfo("Hazduhr the Abbot", 110, Rarity.RARE, mage.cards.h.HazduhrTheAbbot.class)); cards.add(new SetCardInfo("Headstone", 15, Rarity.COMMON, mage.cards.h.Headstone.class)); cards.add(new SetCardInfo("Hungry Mist", 60, Rarity.COMMON, mage.cards.h.HungryMist.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Hungry Mist", 61, Rarity.COMMON, mage.cards.h.HungryMist.class, NON_FULL_USE_VARIOUS)); @@ -131,11 +135,13 @@ public class Homelands extends ExpansionSet { cards.add(new SetCardInfo("Irini Sengir", 17, Rarity.UNCOMMON, mage.cards.i.IriniSengir.class)); cards.add(new SetCardInfo("Jinx", 36, Rarity.COMMON, mage.cards.j.Jinx.class)); cards.add(new SetCardInfo("Joven", 97, Rarity.COMMON, mage.cards.j.Joven.class)); + cards.add(new SetCardInfo("Joven's Tools", 133, Rarity.UNCOMMON, mage.cards.j.JovensTools.class)); cards.add(new SetCardInfo("Koskun Falls", 18, Rarity.RARE, mage.cards.k.KoskunFalls.class)); cards.add(new SetCardInfo("Koskun Keep", 139, Rarity.UNCOMMON, mage.cards.k.KoskunKeep.class)); cards.add(new SetCardInfo("Labyrinth Minotaur", 38, Rarity.COMMON, mage.cards.l.LabyrinthMinotaur.class)); cards.add(new SetCardInfo("Leaping Lizard", 63, Rarity.COMMON, mage.cards.l.LeapingLizard.class)); cards.add(new SetCardInfo("Leeches", 111, Rarity.RARE, mage.cards.l.Leeches.class)); + cards.add(new SetCardInfo("Mammoth Harness", 64, Rarity.RARE, mage.cards.m.MammothHarness.class)); cards.add(new SetCardInfo("Marjhan", 39, Rarity.RARE, mage.cards.m.Marjhan.class)); cards.add(new SetCardInfo("Memory Lapse", 40, Rarity.COMMON, mage.cards.m.MemoryLapse.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Memory Lapse", 41, Rarity.COMMON, mage.cards.m.MemoryLapse.class, NON_FULL_USE_VARIOUS)); @@ -145,6 +151,7 @@ public class Homelands extends ExpansionSet { cards.add(new SetCardInfo("Mystic Decree", 43, Rarity.RARE, mage.cards.m.MysticDecree.class)); cards.add(new SetCardInfo("Narwhal", 44, Rarity.RARE, mage.cards.n.Narwhal.class)); cards.add(new SetCardInfo("Primal Order", 65, Rarity.RARE, mage.cards.p.PrimalOrder.class)); + cards.add(new SetCardInfo("Rashka the Slayer", 115, Rarity.RARE, mage.cards.r.RashkaTheSlayer.class)); cards.add(new SetCardInfo("Reef Pirates", 45, Rarity.COMMON, ReefPirates.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Reef Pirates", 46, Rarity.COMMON, ReefPirates.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Renewal", 66, Rarity.COMMON, mage.cards.r.Renewal.class)); From a06f6244a7b8f84f924d77362b9e5353e185de49 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Tue, 2 Jan 2018 05:58:31 +0100 Subject: [PATCH 37/45] Implemented cards --- Mage.Sets/src/mage/sets/MastersEditionII.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/sets/MastersEditionII.java b/Mage.Sets/src/mage/sets/MastersEditionII.java index ec1bba35f8f..5d6f806cd9a 100644 --- a/Mage.Sets/src/mage/sets/MastersEditionII.java +++ b/Mage.Sets/src/mage/sets/MastersEditionII.java @@ -93,6 +93,7 @@ public class MastersEditionII extends ExpansionSet { cards.add(new SetCardInfo("Brassclaw Orcs", 119, Rarity.COMMON, BrassclawOrcs.class)); cards.add(new SetCardInfo("Brimstone Dragon", 120, Rarity.RARE, mage.cards.b.BrimstoneDragon.class)); cards.add(new SetCardInfo("Brine Shaman", 80, Rarity.COMMON, mage.cards.b.BrineShaman.class)); + cards.add(new SetCardInfo("Broken Visage", 81, Rarity.UNCOMMON, mage.cards.b.BrokenVisage.class)); cards.add(new SetCardInfo("Browse", 43, Rarity.UNCOMMON, mage.cards.b.Browse.class)); cards.add(new SetCardInfo("Burnout", 121, Rarity.UNCOMMON, mage.cards.b.Burnout.class)); cards.add(new SetCardInfo("Carapace", 155, Rarity.COMMON, mage.cards.c.Carapace.class)); From d4588fbf48166403bc93bc7103efda2485e6a914 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Tue, 2 Jan 2018 05:58:58 +0100 Subject: [PATCH 38/45] Implemented cards --- Mage.Sets/src/mage/sets/MastersEdition.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/sets/MastersEdition.java b/Mage.Sets/src/mage/sets/MastersEdition.java index eb9d2ee2936..c0c6451fead 100644 --- a/Mage.Sets/src/mage/sets/MastersEdition.java +++ b/Mage.Sets/src/mage/sets/MastersEdition.java @@ -76,6 +76,7 @@ public class MastersEdition extends ExpansionSet { cards.add(new SetCardInfo("Armageddon", 4, Rarity.RARE, mage.cards.a.Armageddon.class)); cards.add(new SetCardInfo("Artifact Blast", 85, Rarity.COMMON, mage.cards.a.ArtifactBlast.class)); cards.add(new SetCardInfo("Ashnod's Transmogrant", 152, Rarity.COMMON, mage.cards.a.AshnodsTransmogrant.class)); + cards.add(new SetCardInfo("Autumn Willow", 113, Rarity.RARE, mage.cards.a.AutumnWillow.class)); cards.add(new SetCardInfo("Balduvian Horde", 86, Rarity.RARE, mage.cards.b.BalduvianHorde.class)); cards.add(new SetCardInfo("Ball Lightning", 87, Rarity.RARE, mage.cards.b.BallLightning.class)); cards.add(new SetCardInfo("Baron Sengir", 58, Rarity.RARE, mage.cards.b.BaronSengir.class)); From d2fdc26ef1c7abb2aa73080b15c17b449a271e89 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Tue, 2 Jan 2018 05:59:36 +0100 Subject: [PATCH 39/45] Implemented cards --- Mage.Sets/src/mage/sets/FifthEdition.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Mage.Sets/src/mage/sets/FifthEdition.java b/Mage.Sets/src/mage/sets/FifthEdition.java index 23672aa5d01..1afb371eef6 100644 --- a/Mage.Sets/src/mage/sets/FifthEdition.java +++ b/Mage.Sets/src/mage/sets/FifthEdition.java @@ -80,6 +80,7 @@ public class FifthEdition extends ExpansionSet { cards.add(new SetCardInfo("Brainwash", 289, Rarity.COMMON, mage.cards.b.Brainwash.class)); cards.add(new SetCardInfo("Brassclaw Orcs", 213, Rarity.COMMON, BrassclawOrcs.class)); cards.add(new SetCardInfo("Breeding Pit", 10, Rarity.UNCOMMON, mage.cards.b.BreedingPit.class)); + cards.add(new SetCardInfo("Broken Visage", 11, Rarity.RARE, mage.cards.b.BrokenVisage.class)); cards.add(new SetCardInfo("Brothers of Fire", 214, Rarity.COMMON, mage.cards.b.BrothersOfFire.class)); cards.add(new SetCardInfo("Brushland", 412, Rarity.RARE, mage.cards.b.Brushland.class)); cards.add(new SetCardInfo("Carapace", 143, Rarity.COMMON, mage.cards.c.Carapace.class)); @@ -242,6 +243,7 @@ public class FifthEdition extends ExpansionSet { cards.add(new SetCardInfo("Jester's Cap", 385, Rarity.RARE, mage.cards.j.JestersCap.class)); cards.add(new SetCardInfo("Johtull Wurm", 168, Rarity.UNCOMMON, mage.cards.j.JohtullWurm.class)); cards.add(new SetCardInfo("Jokulhaups", 246, Rarity.RARE, mage.cards.j.Jokulhaups.class)); + cards.add(new SetCardInfo("Joven's Tools", 386, Rarity.UNCOMMON, mage.cards.j.JovensTools.class)); cards.add(new SetCardInfo("Justice", 317, Rarity.UNCOMMON, mage.cards.j.Justice.class)); cards.add(new SetCardInfo("Juxtapose", 95, Rarity.RARE, mage.cards.j.Juxtapose.class)); cards.add(new SetCardInfo("Karma", 318, Rarity.UNCOMMON, mage.cards.k.Karma.class)); From 8d0079bd3da1fd897d3ed1f4798d10dc5451994c Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Tue, 2 Jan 2018 15:00:27 +0100 Subject: [PATCH 40/45] [RIX] Updated mtg-cards-data.txt with spoilers of 2018-01-02 15:00 CET --- Utils/mtg-cards-data.txt | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index 2013cd36113..091ab4951c1 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -32696,15 +32696,39 @@ Forest|Duel Decks: Mind vs. Might|65|L||Basic Land - Forest|||{T}: Add {G} to yo Chandra, Gremlin Wrangler|Heroes of the Realm|1|M|{2}{R}{R}|Legendary Planeswalker - Chandra|3||+1: Create a 2/2 red Gremlin creature token.$-2:Chandra, Gremlin Wrangler deals X damage to target creature or player, where X is the number of Gremlins you control.| Dungeon Master|Heroes of the Realm|1|M|{2}{W}{U}|Legendary Planeswalker - Dungeon Master|||+1: Target opponent creates a 1/1 black Skeleton creature token with “When this creature dies, each opponent loses 2 life.”$+1: Roll a d20. If you roll a 1, skip your next turn. If you roll a 12 or higher, draw a card.$-6: You get an adventuring party. (Your party is a 3/3 red Fighter with first strike, a 1/1 white Cleric with lifelink, a 2/2 black Rogue with hexproof, and a 1/1 blue Wizard with flying.)| Nira, Hellkite Duelist|Heroes of the Realm|3|M|{W}{U}{B}{R}{G}|Legendary Creature — Dragon|6|6|Flash$Flying, trample, haste$When Nira, Hellkite Duelist enters the battlefield, the next time you would lose the game this turn, instead draw three cards and your life total becomes 5.| +Paladin of Atonement|Rivals of Ixalan|16|R|{1}{W}|Creature - Vampire Knight|1|1|At the beginning of each upkeep, if you lost life last turn, put a +1/+1 counter on Paladin of Atonement.$When Paladin of Atonement dies, you gain life equal to it's toughness.| +Glorious Destiny|Rivals of Ixalan|18|R|{2}{W}|Enchantment|||Ascend (If you control ten or more permanents, you get the city's blessing for the rest of the game.)$As Glorious Destiny enters the battlefield, choose a creature type.$Creatures you control of the chosen type get +1/+1. They have vigilance as long as you have the city's blessing.| +Shrine Altisaur|Rivals of Ixalan|28|R|{4}{W}|Creature - Dinosaur|3|4|If a source would deal damage to another Dinosaur you control, prevent all but 1 of that damage.| +Zetalpa, Primal Dawn|Rivals of Ixalan|30|R|{6}{W}{W}|Legendary Creature - Elder Dinosaur|4|8|Flying, double strike, vigilance, trample, indestructible| +River Darter|Rivals of Ixalan|47|C|{2}{U}|Creature - Merfolk Warrior|2|3|River Darter can't be blocked by Dinosaurs.| +Seafloor Oracle|Rivals of Ixalan|51|R|{2}{W}{W}|Creature - Merfolk Wizard|2|3|Whenever a Merfolk you controls deals combat damage to a player, draw a card.| +Secrets of the Golden City|Rivals of Ixalan|52|C|{1}{U}{U}|Sorcery|||Ascend (If you control ten or more permanents, you get the city's blessing for the rest of the game.)$Draw two cards. If you have the city's blessing, draw three cards instead.| Silvergill Adept|Rivals of Ixalan|53|U|{1}{U}|Creature - Merfolk Wizard|2|1|As an additional cost to cast Silvergill Adept, reveal a Merfolk card from your hand or pay {3}.$When Silvergill Adept enters the battlefield, draw a card.| +Dusk Charger|Rivals of Ixalan|69|C|{3}{B}|Creature - Horse|3|3|Ascend (If you control ten or more permanents, you get the city's blessing for the rest of the game.)$Dusk Charger gets +2/+2 as long as you have the city's blessing.| Tetzimoc, Primal Death|Rivals of Ixalan|86|R|{4}{B}{B}|Legendary Creature - Elder Dinosaur|6|6|Deathtouch${B}, Reveal Tetzimoc, Primal Death from your hand: Put a prey counter on target creature. Activate this ability only during your turn.$When Tetzimoc, Primal Death enters the battlefield, destroy each creature your opponents control with a prey counter on it.| Vona's Hunger|Rivals of Ixalan|90|R|{2}{B}|Instant|||Ascend (If you control ten or more permanents, you get the city's blessing for the rest of the game.)$Each opponent sacrifices a creature. If you have the city's blessing, instead each opponent sacrifices half the creatures he or she controls rounded up.| Brass's Bounty|Rivals of Ixalan|94|R|{6}{R}|Sorcery|||For each land you control, create a colorless Treasure artifact token with "{t}, Sacrifice this artifact: Add one mana of any color to your mana pool."| +Form of the Dinosaur|Rivals of Ixalan|103|R|{4}{R}{R}|Enchantment|||When Form of the Dinosaur enters the battlefield, your life total becomes 15.$At the beginning of your upkeep, Form of the Dinosaur deals 15 damage to target creature an opponent controls and that creature deals damage equal to its power to you.| +Frilled Deathspitter|Rivals of Ixalan|104|C|{2}{R}|Creature - Dinosaur|3|2|Enrage — Whenever Frilled Deathspitter is dealt damage, it deals 2 damage to target opponent.| +Swaggering Corsair|Rivals of Ixalan|119|C|{2}{R}|Creature - Human Pirate|2|2|Raid — Swaggering Corsair enters the battlefield with a +1/+1 counter on it if you attacked with a creature this turn.| +Tilonalli's Summoner|Rivals of Ixalan|121|R|{1}{R}|Creature - Human Shaman|1|1|Ascend (If you control ten or more permanents, you get the city's blessing for the rest of the game.)$Whenever Tilonalli's Summoner attacks, you may pay {X}{R}. If you do, create X 1/1 Elemental creature tokens tapped and attacking. Exile them at the beginning of the next end step, unless you have the City's blessing.| +Enter the Unknown|Rivals of Ixalan|128|U|{G}|Sorcery|||Target creature you control explores. (Reveal the top card of your library. Put that card into your hand if it's a land. Otherwise, put a +1/+1 counter on this creature, then put the card back or put it into your graveyard.) $You may play an additional land this turn.| Ghalta, Primal Hunger|Rivals of Ixalan|130|R|{1}{0}{G}{G}|Legendary Creature - Elder Dinosaur|12|12|Ghalta, Primal Hunger costs {X} less to cast, where X is the total power of creatures you control.$Trample| +Path to Discovery|Rivals of Ixalan|142|R|{3}{G}|Enchantment|||Whenever a creature enters the battlefield under your control, it explores. (Reveal the top card of your library. Put that card into your hand if it's a land. Otherwise, put a +1/+1 counter on the creature, then put the card back or put it into your graveyard.)| +Tendershoot Dryad|Rivals of Ixalan|147|R|{4}{G}|Creature - Dryad|2|2|Ascend (If you control ten or more permanents, you get the city's blessing for the rest of the game.)$At the beginning of each upkeep, create a 1/1 green Saproling creature token.$Saprolings you control get +2/+2 as long as you have the city's blessing.| +Thrashing Brontodon|Rivals of Ixalan|148|U|{1}{G}{G}|Creature - Dinosaur|3|4|{1}, Sacrificing Thrashing Brontodon: Destroy target artifact or enchantment.| +Angrath, the Flame-Chained|Rivals of Ixalan|152|M|{3}{B}{R}|Legendary Planeswalker - Angrath|4|+1: Each opponent discards a card and loses 2 life.$-3: Gain control of target creature until end of turn. Untap it. It gains haste until end of turn. Sacrifice it at the beginning of the next end step if it has converted mana cost 3 or less.$-8: Each opponent loses life equal to the number of cards in his or her graveyard.| +Atzocan Seer|Rivals of Ixalan|153|U|{1}{G}{W}|Creature - Human Druid|2|3|{t}: Add one mana of any color to your manan pool.$Sacrifice Atzocan Seer: Return target Dinosaur card from your graveyard to your hand.| +Elenda, the Dusk Rose|Rivals of Ixalan|157|M|{2}{W}{B}|Legendary Creature - Vampire Knight|1|1|Lifelink$Whenever another creature dies, put a +1/+1 counter on Elenda, the Dusk Rose.$When Elenda dies, create X 1/1 white Vampire creature tokens with lifelink, where X is Elenda's power| +Journey to Eternity|Rivals of Ixalan|160|R|{1}{B}{G}|Legendary Enchantment - Aura|||Enchant creature you control$When enchanted creature dies, return it to the battlefield under your control, then return Journey to Eternity to the battlefield transformed under your control.| +Atzal, Cave of Eternity|Rivals of Ixalan|160|R||Legendary Land|||(Transforms from Journey to Eternity.)${t}: Add one mana of any color to your mana pool.${3}{B}{G}, {t}: Return target creature card from your graveyard to the battlefield.| +Kumena, Tyrant of Orzca|Rivals of Ixalan|162|M|{1}{G}{U}|Legendary Creature - Merfolk Shaman|2|4|Tap another untapped Merfolk you control: Kumena, Tyrant of Orzca can't be blocked this turn.$Tap three untapped Merfolk you control: Draw a card.$Tap five untapped Merfolk you control: Put a +1/+1 counter on each Merfolk you control.| Storm the Vault|Rivals of Ixalan|173|R|{2}{U}{R}|Legendary Enchantment|||Whenever one or more creatures you control deal combat damage to a player, create a colorless Treasure artifact token with "{T}, Sacrifice this artifact: Add one mana of any color to your mana pool."$At the beginning of your end step, if you control five or more artifacts, transform Storm the Vault.| Vault of Catlacan|Rivals of Ixalan|173|R||Legendary Land|||(Transforms from Storm the Vault.)${T}: Add one mana of any color to your mana pool.${T}: Add {U} to your mana pool for each artifact you control.| +Awakened Amalgamation|Rivals of Ixalan|175|R|{4}|Artifact Creature - Golem|0|0|Awakened Amalgamation's power and toughness are each equal to the number of lands you control with different names.| Captain's Hook|Rivals of Ixalan|177|R|{3}|Artifact - Equipment|||Equipped creature gets +2/+0, has menace, and is a Pirate in addition to its other creature types.$Whenever Captain's Hook becomes unattached from a permanent, destroy that permanent.$Equip {1}| -The Immortal Sun|Rivals of Ixalan|180|M|{6}|Legendary Artifact|||Players can't activate loyalty abilities of planeswalkers.$At the beginning of your draw step, draw an additional card.$Spells you cast cost {1} less to cast.$Creatures you control get +1/+1.| +Gleaming Barrier|Rivals of Ixalan|178|C|{2}|Artifact Creature - Wall|0|4|Defender$When Gleaming Barrier dies, create a colorless Treasure artifact token with "{t}, Sacrifice this artifact: Add one mana of any color to your mana pool."| +The Immortal Sun|Rivals of Ixalan|180|M|{6}|Legendary Artifact|||Players can't activate planeswalkers' loyalty abilities.$At the beginning of your draw step, draw an additional card.$Spells you cast cost {1} less to cast.$Creatures you control get +1/+1.| Evolving Wilds|Rivals of Ixalan|186|C||Land|||{T}, Sacrifice Evolving Wilds: Search your library for a basic land card, put it onto the battlefield tapped, then shuffle your library.| Vraska, Scheming Gorgon|Rivals of Ixalan|197|M|{4}{B}{B}|Legendary Planeswalker - Vraska|5|+2: Creatures you control get +1/+0 until end of turn.$-3: Destroy target creature.$-10: Until end of turn, creatures you control gain deathtouch and "Whenever this creature deals damage to an opponent, that player loses the game."| Vampire Champion|Rivals of Ixalan|198|C|{3}{B}|Creature - Vampire Soldier|3|3|Deathtouch (Any amount of damage this deals to a creature is enough to destroy it.)| From 70c08a2d810565a59b98831a47f22fe78da928a8 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Tue, 2 Jan 2018 15:01:58 +0100 Subject: [PATCH 41/45] * Victimize - Fixed that continuous effects of sacrificed permanent did not end before returning targets from graveyard (fixes #4315). --- Mage.Sets/src/mage/cards/v/Victimize.java | 3 ++- .../java/mage/abilities/effects/common/DoIfCostPaid.java | 7 +++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Mage.Sets/src/mage/cards/v/Victimize.java b/Mage.Sets/src/mage/cards/v/Victimize.java index 509f29e36f1..5ff6e591e1f 100644 --- a/Mage.Sets/src/mage/cards/v/Victimize.java +++ b/Mage.Sets/src/mage/cards/v/Victimize.java @@ -51,7 +51,7 @@ import mage.target.common.TargetControlledCreaturePermanent; public class Victimize extends CardImpl { public Victimize(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{2}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{B}"); // Choose two target creature cards in your graveyard. Sacrifice a creature. If you do, return the chosen cards to the battlefield tapped. this.getSpellAbility().addEffect(new VictimizeEffect()); @@ -90,6 +90,7 @@ class VictimizeEffect extends OneShotEffect { if (controller != null) { SacrificeTargetCost cost = new SacrificeTargetCost(new TargetControlledCreaturePermanent(new FilterControlledCreaturePermanent("a creature"))); if (cost.pay(source, game, source.getSourceId(), source.getControllerId(), false, null)) { + game.applyEffects(); // To end effects of the sacrificed creature controller.moveCards(new CardsImpl(getTargetPointer().getTargets(game, source)).getCards(game), Zone.BATTLEFIELD, source, game, true, false, false, null); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/DoIfCostPaid.java b/Mage/src/main/java/mage/abilities/effects/common/DoIfCostPaid.java index 75f36ec46ca..c264b34e9d9 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DoIfCostPaid.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DoIfCostPaid.java @@ -77,6 +77,7 @@ public class DoIfCostPaid extends OneShotEffect { && (!optional || player.chooseUse(executingEffects.get(0).getOutcome(), message, source, game))) { cost.clearPaid(); if (cost.pay(source, game, source.getSourceId(), player.getId(), false)) { + game.applyEffects(); // To end effects e.g. of sacrificed permanents for (Effect effect : executingEffects) { effect.setTargetPointer(this.targetPointer); if (effect instanceof OneShotEffect) { @@ -86,8 +87,7 @@ public class DoIfCostPaid extends OneShotEffect { } } player.resetStoredBookmark(game); // otherwise you can e.g. undo card drawn with Mentor of the Meek - } - else if (!otherwiseEffects.isEmpty()) { + } else if (!otherwiseEffects.isEmpty()) { for (Effect effect : otherwiseEffects) { effect.setTargetPointer(this.targetPointer); if (effect instanceof OneShotEffect) { @@ -97,8 +97,7 @@ public class DoIfCostPaid extends OneShotEffect { } } } - } - else if (!otherwiseEffects.isEmpty()) { + } else if (!otherwiseEffects.isEmpty()) { for (Effect effect : otherwiseEffects) { effect.setTargetPointer(this.targetPointer); if (effect instanceof OneShotEffect) { From 050a70b7dbbfd1ec6af14854ffad92847480492c Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Tue, 2 Jan 2018 19:38:08 +0400 Subject: [PATCH 42/45] UI: fixed #4329 - errors on card selection in linux (debian) --- .../card/arcane/ManaSymbolsCellRenderer.java | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbolsCellRenderer.java b/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbolsCellRenderer.java index 1de0abceb11..46a0e3f1c2c 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbolsCellRenderer.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbolsCellRenderer.java @@ -35,28 +35,30 @@ public final class ManaSymbolsCellRenderer extends DefaultTableCellRenderer { String manaCost = (String)value; manaPanel.removeAll(); manaPanel.setLayout(new BoxLayout(manaPanel, BoxLayout.X_AXIS)); - StringTokenizer tok = new StringTokenizer(manaCost, " "); - while (tok.hasMoreTokens()) { - String symbol = tok.nextToken(); + if(manaCost != null){ + StringTokenizer tok = new StringTokenizer(manaCost, " "); + while (tok.hasMoreTokens()) { + String symbol = tok.nextToken(); - JLabel symbolLabel = new JLabel(); - //symbolLabel.setBorder(new LineBorder(new Color(150, 150, 150))); // debug - symbolLabel.setBorder(new EmptyBorder(0, symbolHorizontalMargin,0, 0)); + JLabel symbolLabel = new JLabel(); + //symbolLabel.setBorder(new LineBorder(new Color(150, 150, 150))); // debug + symbolLabel.setBorder(new EmptyBorder(0, symbolHorizontalMargin,0, 0)); - BufferedImage image = ManaSymbols.getSizedManaSymbol(symbol, symbolWidth); - if (image != null){ - // icon - symbolLabel.setIcon(new ImageIcon(image)); - }else - { - // text - symbolLabel.setText("{" + symbol + "}"); - symbolLabel.setOpaque(baseLabel.isOpaque()); - symbolLabel.setForeground(baseLabel.getForeground()); - symbolLabel.setBackground(baseLabel.getBackground()); + BufferedImage image = ManaSymbols.getSizedManaSymbol(symbol, symbolWidth); + if (image != null){ + // icon + symbolLabel.setIcon(new ImageIcon(image)); + }else + { + // text + symbolLabel.setText("{" + symbol + "}"); + symbolLabel.setOpaque(baseLabel.isOpaque()); + symbolLabel.setForeground(baseLabel.getForeground()); + symbolLabel.setBackground(baseLabel.getBackground()); + } + + manaPanel.add(symbolLabel); } - - manaPanel.add(symbolLabel); } return manaPanel; From 988076b93c1e8aacae4077a53d2735b5dd83fd97 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Tue, 2 Jan 2018 16:44:31 +0100 Subject: [PATCH 43/45] Minor fix --- Mage.Sets/src/mage/cards/a/AutumnWillow.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/a/AutumnWillow.java b/Mage.Sets/src/mage/cards/a/AutumnWillow.java index 16829e0c7d9..7ccb1202e4a 100644 --- a/Mage.Sets/src/mage/cards/a/AutumnWillow.java +++ b/Mage.Sets/src/mage/cards/a/AutumnWillow.java @@ -100,7 +100,7 @@ class AutumnWillowEffect extends AsThoughEffectImpl { if (affectedControllerId.equals(source.getFirstTarget())) { Permanent creature = game.getPermanent(sourceId); if (creature != null) { - if (sourceId == source.getSourceId()) { + if (sourceId.equals(source.getSourceId())) { return true; } } From 7d026c699fccdcd0329fd3b98393fea9f1b58078 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Tue, 2 Jan 2018 17:47:38 +0100 Subject: [PATCH 44/45] [RIX] Added 3 cards. --- Mage.Sets/src/mage/cards/a/ArdentRecruit.java | 8 +- Mage.Sets/src/mage/cards/d/DuskCharger.java | 78 +++++++++ Mage.Sets/src/mage/cards/f/FirstResponse.java | 42 +---- .../src/mage/cards/g/GloriousDestiny.java | 92 ++++++++++ .../src/mage/cards/p/PaladinOfAtonement.java | 79 +++++++++ Mage.Sets/src/mage/sets/RivalsOfIxalan.java | 159 +++++++++--------- .../common/LiveLostLastTurnCondition.java | 54 ++++++ .../ConditionalTriggeredAbility.java | 14 +- .../effects/keyword/AscendEffect.java | 23 +-- .../mage/abilities/keyword/AscendAbility.java | 115 +++++++++++++ .../main/java/mage/filter/StaticFilters.java | 3 + .../main/java/mage/watchers/WatcherUtils.java | 46 +++++ .../common/PlayerLostLifeWatcher.java | 2 +- Utils/keywords.txt | 1 + 14 files changed, 571 insertions(+), 145 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/d/DuskCharger.java create mode 100644 Mage.Sets/src/mage/cards/g/GloriousDestiny.java create mode 100644 Mage.Sets/src/mage/cards/p/PaladinOfAtonement.java create mode 100644 Mage/src/main/java/mage/abilities/condition/common/LiveLostLastTurnCondition.java create mode 100644 Mage/src/main/java/mage/abilities/keyword/AscendAbility.java create mode 100644 Mage/src/main/java/mage/watchers/WatcherUtils.java diff --git a/Mage.Sets/src/mage/cards/a/ArdentRecruit.java b/Mage.Sets/src/mage/cards/a/ArdentRecruit.java index 1fe2d66ec16..c793bb04351 100644 --- a/Mage.Sets/src/mage/cards/a/ArdentRecruit.java +++ b/Mage.Sets/src/mage/cards/a/ArdentRecruit.java @@ -25,7 +25,6 @@ * authors and should not be interpreted as representing official policies, either expressed * or implied, of BetaSteward_at_googlemail.com. */ - package mage.cards.a; import java.util.UUID; @@ -46,14 +45,17 @@ import mage.constants.*; public class ArdentRecruit extends CardImpl { public ArdentRecruit(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{W}"); + 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); + + // Metalcraft — Ardent Recruit gets +2/+2 as long as you control three or more artifacts. ContinuousEffect boostSource = new BoostSourceEffect(2, 2, Duration.WhileOnBattlefield); - ConditionalContinuousEffect effect = new ConditionalContinuousEffect(boostSource, MetalcraftCondition.instance, "Ardent Recruit gets +2/+2 as long as you control three or more artifacts"); + ConditionalContinuousEffect effect = new ConditionalContinuousEffect(boostSource, MetalcraftCondition.instance, + "{this} gets +2/+2 as long as you control three or more artifacts"); Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, effect); ability.setAbilityWord(AbilityWord.METALCRAFT); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/d/DuskCharger.java b/Mage.Sets/src/mage/cards/d/DuskCharger.java new file mode 100644 index 00000000000..e9a09ec133f --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DuskCharger.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.d; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.CitysBlessingCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.keyword.AscendAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.Zone; + +/** + * + * @author LevelX2 + */ +public class DuskCharger extends CardImpl { + + public DuskCharger(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); + + this.subtype.add(SubType.HORSE); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Ascend + this.addAbility(new AscendAbility()); + + // Dusk Charger gets +2/+2 as long as you have the city's blessing. + ContinuousEffect boostSource = new BoostSourceEffect(2, 2, Duration.WhileOnBattlefield); + ConditionalContinuousEffect effect = new ConditionalContinuousEffect(boostSource, CitysBlessingCondition.instance, + "{this} gets +2/+2 as long as you have the city's blessing"); + Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, effect); + this.addAbility(ability); + } + + public DuskCharger(final DuskCharger card) { + super(card); + } + + @Override + public DuskCharger copy() { + return new DuskCharger(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FirstResponse.java b/Mage.Sets/src/mage/cards/f/FirstResponse.java index 51b618397af..5e249cf39dd 100644 --- a/Mage.Sets/src/mage/cards/f/FirstResponse.java +++ b/Mage.Sets/src/mage/cards/f/FirstResponse.java @@ -28,18 +28,15 @@ package mage.cards.f; import java.util.UUID; -import mage.abilities.Ability; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.condition.common.LiveLostLastTurnCondition; +import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.common.CreateTokenEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.TargetController; -import mage.game.Game; import mage.game.permanent.token.SoldierToken; -import mage.watchers.common.PlayerLostLifeWatcher; /** * @@ -51,8 +48,10 @@ public class FirstResponse extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{W}"); // At the beginning of each upkeep, if you lost life last turn, create a 1/1 white Soldier creature token. - this.addAbility(new BeginningOfUpkeepTriggeredAbility(new FirstResponseEffect(), TargetController.ANY, false), new PlayerLostLifeWatcher()); - + this.addAbility(new ConditionalTriggeredAbility( + new BeginningOfUpkeepTriggeredAbility(new CreateTokenEffect(new SoldierToken()), TargetController.ANY, false), + LiveLostLastTurnCondition.instance, + "At the beginning of each upkeep, if you lost life last turn, create a 1/1 white Soldier creature token.")); } public FirstResponse(final FirstResponse card) { @@ -64,32 +63,3 @@ public class FirstResponse extends CardImpl { return new FirstResponse(this); } } - -class FirstResponseEffect extends OneShotEffect { - - public FirstResponseEffect() { - super(Outcome.PutCreatureInPlay); - this.staticText = "if you lost life last turn, create a 1/1 white Soldier creature token"; - } - - public FirstResponseEffect(final FirstResponseEffect effect) { - super(effect); - } - - @Override - public FirstResponseEffect copy() { - return new FirstResponseEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - PlayerLostLifeWatcher watcher = (PlayerLostLifeWatcher) game.getState().getWatchers().get(PlayerLostLifeWatcher.class.getSimpleName()); - if (watcher != null) { - if (watcher.getLiveLostLastTurn(source.getControllerId()) > 0) { - return new CreateTokenEffect(new SoldierToken()).apply(game, source); - } - return true; - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/g/GloriousDestiny.java b/Mage.Sets/src/mage/cards/g/GloriousDestiny.java new file mode 100644 index 00000000000..ec88a4e9ef9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GloriousDestiny.java @@ -0,0 +1,92 @@ +/* + * 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.UUID; +import mage.abilities.Ability; +import mage.abilities.common.AsEntersBattlefieldAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.CitysBlessingCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.common.ChooseCreatureTypeEffect; +import mage.abilities.effects.common.continuous.BoostAllOfChosenSubtypeEffect; +import mage.abilities.effects.common.continuous.GainAbilityAllOfChosenSubtypeEffect; +import mage.abilities.keyword.AscendAbility; +import mage.abilities.keyword.VigilanceAbility; +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 static mage.filter.StaticFilters.FILTER_PERMANENT_CREATURES_CONTROLLED; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.ControllerPredicate; + +/** + * + * @author LevelX2 + */ +public class GloriousDestiny extends CardImpl { + + private final static FilterCreaturePermanent filter = new FilterCreaturePermanent("creatures you control of the chosen type"); + + static { + filter.add(new ControllerPredicate(TargetController.YOU)); + } + + public GloriousDestiny(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}"); + + // Ascend + this.addAbility(new AscendAbility()); + + // As Glorious Destiny enters the battlefield, choose a creature type. + this.addAbility(new AsEntersBattlefieldAbility(new ChooseCreatureTypeEffect(Outcome.BoostCreature))); + + // Creatures you control of the chosen type get +1/+1. They have vigilance as long as you have the city's blessing. + Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostAllOfChosenSubtypeEffect(1, 1, Duration.WhileOnBattlefield, filter, true)); + ContinuousEffect effect = new ConditionalContinuousEffect( + new GainAbilityAllOfChosenSubtypeEffect(VigilanceAbility.getInstance(), Duration.WhileOnBattlefield, FILTER_PERMANENT_CREATURES_CONTROLLED), + CitysBlessingCondition.instance, + "They have vigilance as long as you have the city's blessing"); + ability.addEffect(effect); + this.addAbility(ability); + } + + public GloriousDestiny(final GloriousDestiny card) { + super(card); + } + + @Override + public GloriousDestiny copy() { + return new GloriousDestiny(this); + } +} diff --git a/Mage.Sets/src/mage/cards/p/PaladinOfAtonement.java b/Mage.Sets/src/mage/cards/p/PaladinOfAtonement.java new file mode 100644 index 00000000000..941c0495abe --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PaladinOfAtonement.java @@ -0,0 +1,79 @@ +/* + * 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.p; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.common.DiesTriggeredAbility; +import mage.abilities.condition.common.LiveLostLastTurnCondition; +import mage.abilities.decorator.ConditionalTriggeredAbility; +import mage.abilities.dynamicvalue.common.SourcePermanentToughnessValue; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.counters.CounterType; + +/** + * + * @author LevelX2 + */ +public class PaladinOfAtonement extends CardImpl { + + public PaladinOfAtonement(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.VAMPIRE); + this.subtype.add(SubType.KNIGHT); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // At the beginning of each upkeep, if you lost life last turn, put a +1/+1 counter on Paladin of Atonement. + this.addAbility(new ConditionalTriggeredAbility( + new BeginningOfUpkeepTriggeredAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance()), TargetController.ANY, false), + LiveLostLastTurnCondition.instance, + "At the beginning of each upkeep, if you lost life last turn, put a +1/+1 counter on {this}")); + + // When Paladin of Atonement dies, you gain life equal to it's toughness. + this.addAbility(new DiesTriggeredAbility(new GainLifeEffect(SourcePermanentToughnessValue.getInstance(), + "you gain life equal to it's toughness"))); + } + + public PaladinOfAtonement(final PaladinOfAtonement card) { + super(card); + } + + @Override + public PaladinOfAtonement copy() { + return new PaladinOfAtonement(this); + } +} diff --git a/Mage.Sets/src/mage/sets/RivalsOfIxalan.java b/Mage.Sets/src/mage/sets/RivalsOfIxalan.java index 710c3994e78..3aab703e2f3 100644 --- a/Mage.Sets/src/mage/sets/RivalsOfIxalan.java +++ b/Mage.Sets/src/mage/sets/RivalsOfIxalan.java @@ -1,78 +1,81 @@ -/* -* 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.sets; - -import mage.cards.ExpansionSet; -import mage.constants.Rarity; -import mage.constants.SetType; - -/** - * - * @author fireshoes - */ -public class RivalsOfIxalan extends ExpansionSet { - - private static final RivalsOfIxalan instance = new RivalsOfIxalan(); - - public static RivalsOfIxalan getInstance() { - return instance; - } - - private RivalsOfIxalan() { - super("Rivals of Ixalan", "RIX", ExpansionSet.buildDate(2018, 1, 19), SetType.EXPANSION); - this.blockName = "Ixalan"; - this.parentSet = Ixalan.getInstance(); - this.hasBoosters = true; - this.hasBasicLands = false; - this.numBoosterLands = 1; - this.numBoosterCommon = 11; - this.numBoosterUncommon = 3; - this.numBoosterRare = 1; - this.ratioBoosterMythic = 8; - - cards.add(new SetCardInfo("Angrath's Ambusher", 202, Rarity.UNCOMMON, mage.cards.a.AngrathsAmbusher.class)); - cards.add(new SetCardInfo("Angrath's Fury", 204, Rarity.RARE, mage.cards.a.AngrathsFury.class)); - cards.add(new SetCardInfo("Angrath, Minotaur Pirate", 201, Rarity.MYTHIC, mage.cards.a.AngrathMinotaurPirate.class)); - cards.add(new SetCardInfo("Brass's Bounty", 94, Rarity.RARE, mage.cards.b.BrasssBounty.class)); - cards.add(new SetCardInfo("Captain's Hook", 177, Rarity.RARE, mage.cards.c.CaptainsHook.class)); - cards.add(new SetCardInfo("Cinder Barrens", 205, Rarity.RARE, mage.cards.c.CinderBarrens.class)); - cards.add(new SetCardInfo("Evolving Wilds", 186, Rarity.RARE, mage.cards.e.EvolvingWilds.class)); - cards.add(new SetCardInfo("Ghalta, Primal Hunger", 130, Rarity.RARE, mage.cards.g.GhaltaPrimalHunger.class)); - cards.add(new SetCardInfo("Silvergill Adept", 53, Rarity.UNCOMMON, mage.cards.s.SilvergillAdept.class)); - cards.add(new SetCardInfo("Storm the Vault", 173, Rarity.RARE, mage.cards.s.StormTheVault.class)); - cards.add(new SetCardInfo("Swab Goblin", 203, Rarity.COMMON, mage.cards.s.SwabGoblin.class)); - cards.add(new SetCardInfo("Tetzimoc, Primal Death", 86, Rarity.RARE, mage.cards.t.TetzimocPrimalDeath.class)); - cards.add(new SetCardInfo("The Immortal Sun", 180, Rarity.MYTHIC, mage.cards.t.TheImmortalSun.class)); - cards.add(new SetCardInfo("Vampire Champion", 198, Rarity.COMMON, mage.cards.v.VampireChampion.class)); - cards.add(new SetCardInfo("Vault of Catlacan", 173, Rarity.RARE, mage.cards.v.VaultOfCatlacan.class)); - cards.add(new SetCardInfo("Vona's Hunger", 90, Rarity.RARE, mage.cards.v.VonasHunger.class)); - cards.add(new SetCardInfo("Vraska's Conquistador", 199, Rarity.UNCOMMON, mage.cards.v.VraskasConquistador.class)); - cards.add(new SetCardInfo("Vraska's Scorn", 200, Rarity.RARE, mage.cards.v.VraskasScorn.class)); - cards.add(new SetCardInfo("Vraska, Scheming Gorgon", 197, Rarity.MYTHIC, mage.cards.v.VraskaSchemingGorgon.class)); - } -} +/* +* 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.sets; + +import mage.cards.ExpansionSet; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * + * @author fireshoes + */ +public class RivalsOfIxalan extends ExpansionSet { + + private static final RivalsOfIxalan instance = new RivalsOfIxalan(); + + public static RivalsOfIxalan getInstance() { + return instance; + } + + private RivalsOfIxalan() { + super("Rivals of Ixalan", "RIX", ExpansionSet.buildDate(2018, 1, 19), SetType.EXPANSION); + this.blockName = "Ixalan"; + this.parentSet = Ixalan.getInstance(); + this.hasBoosters = true; + this.hasBasicLands = false; + this.numBoosterLands = 1; + this.numBoosterCommon = 11; + this.numBoosterUncommon = 3; + this.numBoosterRare = 1; + this.ratioBoosterMythic = 8; + + cards.add(new SetCardInfo("Angrath's Ambusher", 202, Rarity.UNCOMMON, mage.cards.a.AngrathsAmbusher.class)); + cards.add(new SetCardInfo("Angrath's Fury", 204, Rarity.RARE, mage.cards.a.AngrathsFury.class)); + cards.add(new SetCardInfo("Angrath, Minotaur Pirate", 201, Rarity.MYTHIC, mage.cards.a.AngrathMinotaurPirate.class)); + cards.add(new SetCardInfo("Brass's Bounty", 94, Rarity.RARE, mage.cards.b.BrasssBounty.class)); + cards.add(new SetCardInfo("Captain's Hook", 177, Rarity.RARE, mage.cards.c.CaptainsHook.class)); + cards.add(new SetCardInfo("Cinder Barrens", 205, Rarity.RARE, mage.cards.c.CinderBarrens.class)); + cards.add(new SetCardInfo("Dusk Charger", 69, Rarity.COMMON, mage.cards.d.DuskCharger.class)); + cards.add(new SetCardInfo("Evolving Wilds", 186, Rarity.RARE, mage.cards.e.EvolvingWilds.class)); + cards.add(new SetCardInfo("Ghalta, Primal Hunger", 130, Rarity.RARE, mage.cards.g.GhaltaPrimalHunger.class)); + cards.add(new SetCardInfo("Glorious Destiny", 18, Rarity.RARE, mage.cards.g.GloriousDestiny.class)); + cards.add(new SetCardInfo("Paladin of Atonement", 16, Rarity.RARE, mage.cards.p.PaladinOfAtonement.class)); + cards.add(new SetCardInfo("Silvergill Adept", 53, Rarity.UNCOMMON, mage.cards.s.SilvergillAdept.class)); + cards.add(new SetCardInfo("Storm the Vault", 173, Rarity.RARE, mage.cards.s.StormTheVault.class)); + cards.add(new SetCardInfo("Swab Goblin", 203, Rarity.COMMON, mage.cards.s.SwabGoblin.class)); + cards.add(new SetCardInfo("Tetzimoc, Primal Death", 86, Rarity.RARE, mage.cards.t.TetzimocPrimalDeath.class)); + cards.add(new SetCardInfo("The Immortal Sun", 180, Rarity.MYTHIC, mage.cards.t.TheImmortalSun.class)); + cards.add(new SetCardInfo("Vampire Champion", 198, Rarity.COMMON, mage.cards.v.VampireChampion.class)); + cards.add(new SetCardInfo("Vault of Catlacan", 173, Rarity.RARE, mage.cards.v.VaultOfCatlacan.class)); + cards.add(new SetCardInfo("Vona's Hunger", 90, Rarity.RARE, mage.cards.v.VonasHunger.class)); + cards.add(new SetCardInfo("Vraska's Conquistador", 199, Rarity.UNCOMMON, mage.cards.v.VraskasConquistador.class)); + cards.add(new SetCardInfo("Vraska's Scorn", 200, Rarity.RARE, mage.cards.v.VraskasScorn.class)); + cards.add(new SetCardInfo("Vraska, Scheming Gorgon", 197, Rarity.MYTHIC, mage.cards.v.VraskaSchemingGorgon.class)); + } +} diff --git a/Mage/src/main/java/mage/abilities/condition/common/LiveLostLastTurnCondition.java b/Mage/src/main/java/mage/abilities/condition/common/LiveLostLastTurnCondition.java new file mode 100644 index 00000000000..61fd4c49fe2 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/condition/common/LiveLostLastTurnCondition.java @@ -0,0 +1,54 @@ +/* + * 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.abilities.condition.common; + +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.game.Game; +import mage.watchers.WatcherUtils; +import mage.watchers.common.PlayerLostLifeWatcher; + +/** + * + * @author LevelX + */ +public enum LiveLostLastTurnCondition implements Condition { + + instance; + + @Override + public boolean apply(Game game, Ability source) { + PlayerLostLifeWatcher watcher = (PlayerLostLifeWatcher) game.getState().getWatchers().get(PlayerLostLifeWatcher.class.getSimpleName()); + if (watcher != null) { + return watcher.getLiveLostLastTurn(source.getControllerId()) > 0; + } else { + WatcherUtils.logMissingWatcher(game, source, PlayerLostLifeWatcher.class, this.getClass()); + } + return false; + } +} diff --git a/Mage/src/main/java/mage/abilities/decorator/ConditionalTriggeredAbility.java b/Mage/src/main/java/mage/abilities/decorator/ConditionalTriggeredAbility.java index 33a9dc10a78..a450227a675 100644 --- a/Mage/src/main/java/mage/abilities/decorator/ConditionalTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/decorator/ConditionalTriggeredAbility.java @@ -19,7 +19,7 @@ public class ConditionalTriggeredAbility extends TriggeredAbilityImpl { protected TriggeredAbility ability; protected Condition condition; - protected String text; + protected String abilityText; /** * Triggered ability with a condition. Set the optionality for the trigger @@ -27,22 +27,22 @@ public class ConditionalTriggeredAbility extends TriggeredAbilityImpl { * * @param ability * @param condition - * @param text if null or empty, the rule text of the triggered ability - * itself is used. + * @param text explicit rule text for the ability, if null or empty, the + * rule text generated by the triggered ability itself is used. */ public ConditionalTriggeredAbility(TriggeredAbility ability, Condition condition, String text) { super(ability.getZone(), null); this.ability = ability; this.modes = ability.getModes(); this.condition = condition; - this.text = text; + this.abilityText = text; } public ConditionalTriggeredAbility(final ConditionalTriggeredAbility triggered) { super(triggered); this.ability = triggered.ability.copy(); this.condition = triggered.condition; - this.text = triggered.text; + this.abilityText = triggered.abilityText; } @Override @@ -69,10 +69,10 @@ public class ConditionalTriggeredAbility extends TriggeredAbilityImpl { @Override public String getRule() { - if (text == null || text.isEmpty()) { + if (abilityText == null || abilityText.isEmpty()) { return ability.getRule(); } - return text; + return abilityText; } @Override diff --git a/Mage/src/main/java/mage/abilities/effects/keyword/AscendEffect.java b/Mage/src/main/java/mage/abilities/effects/keyword/AscendEffect.java index 11d7bbba84d..be290d6eac9 100644 --- a/Mage/src/main/java/mage/abilities/effects/keyword/AscendEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/keyword/AscendEffect.java @@ -29,12 +29,9 @@ package mage.abilities.effects.keyword; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.AscendAbility; import mage.constants.Outcome; -import mage.designations.CitysBlessing; -import mage.designations.DesignationType; -import mage.filter.StaticFilters; import mage.game.Game; -import mage.players.Player; /** * @@ -44,7 +41,7 @@ public class AscendEffect extends OneShotEffect { public AscendEffect() { super(Outcome.Detriment); - staticText = "Ascend (If you control ten or more permanents, you get the city's blessing for the rest of the game.)
"; + staticText = AscendAbility.ASCEND_RULE + "
"; } public AscendEffect(final AscendEffect effect) { @@ -58,20 +55,6 @@ public class AscendEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - if (game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_ARTIFACT_CREATURE_ENCHANTMENT_OR_LAND, controller.getId(), game) > 9) { - if (!controller.hasDesignation(DesignationType.CITYS_BLESSING)) { - controller.addDesignation(new CitysBlessing()); - game.informPlayers(controller.getLogName() + " gets the city's blessing for the rest of the game."); - } else { - game.informPlayers(controller.getLogName() + " already has the city's blessing."); - } - } else { - game.informPlayers(controller.getLogName() + " does not get the city's blessing."); - } - return true; - } - return false; + return AscendAbility.checkAscend(game, source, true); } } diff --git a/Mage/src/main/java/mage/abilities/keyword/AscendAbility.java b/Mage/src/main/java/mage/abilities/keyword/AscendAbility.java new file mode 100644 index 00000000000..0f35ff9d016 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/keyword/AscendAbility.java @@ -0,0 +1,115 @@ +/* + * 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.abilities.keyword; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousEffectImpl; +import static mage.abilities.keyword.AscendAbility.ASCEND_RULE; +import mage.constants.Duration; +import mage.constants.Layer; +import mage.constants.Outcome; +import mage.constants.SubLayer; +import mage.constants.Zone; +import mage.designations.CitysBlessing; +import mage.designations.DesignationType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; + +/** + * + * @author LevelX2 + */ +public class AscendAbility extends SimpleStaticAbility { + + public static String ASCEND_RULE = "Ascend (If you control ten or more permanents, you get the city's blessing for the rest of the game.)"; + + public AscendAbility() { + super(Zone.BATTLEFIELD, new AscendContinuousEffect()); + } + + public AscendAbility(final AscendAbility ability) { + super(ability); + } + + @Override + public AscendAbility copy() { + return new AscendAbility(this); + } + + public static boolean checkAscend(Game game, Ability source, boolean verbose) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + if (!controller.hasDesignation(DesignationType.CITYS_BLESSING)) { + if (game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_ARTIFACT_CREATURE_ENCHANTMENT_OR_LAND, controller.getId(), game) > 9) { + controller.addDesignation(new CitysBlessing()); + game.informPlayers(controller.getLogName() + " gets the city's blessing for the rest of the game."); + } else { + if (verbose) { + game.informPlayers(controller.getLogName() + " does not get the city's blessing."); + } + } + } else { + if (verbose) { + game.informPlayers(controller.getLogName() + " already has the city's blessing."); + } + } + return true; + } + return false; + } + + @Override + public String getRule() { + return ASCEND_RULE; + } + +} + +class AscendContinuousEffect extends ContinuousEffectImpl { + + public AscendContinuousEffect() { + super(Duration.WhileOnBattlefield, Layer.PlayerEffects, SubLayer.NA, Outcome.Benefit); + staticText = ASCEND_RULE; + } + + public AscendContinuousEffect(final AscendContinuousEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return AscendAbility.checkAscend(game, source, false); + } + + @Override + public AscendContinuousEffect copy() { + return new AscendContinuousEffect(this); + } +} diff --git a/Mage/src/main/java/mage/filter/StaticFilters.java b/Mage/src/main/java/mage/filter/StaticFilters.java index 0e7f18bc02e..66006123414 100644 --- a/Mage/src/main/java/mage/filter/StaticFilters.java +++ b/Mage/src/main/java/mage/filter/StaticFilters.java @@ -61,6 +61,7 @@ public final class StaticFilters { public static final FilterCreaturePermanent FILTER_PERMANENT_CREATURE = new FilterCreaturePermanent(); public static final FilterCreaturePermanent FILTER_PERMANENT_A_CREATURE = new FilterCreaturePermanent("a creature"); public static final FilterCreaturePermanent FILTER_PERMANENT_CREATURES = new FilterCreaturePermanent("creatures"); + public static final FilterCreaturePermanent FILTER_PERMANENT_CREATURES_CONTROLLED = new FilterCreaturePermanent("creatures you control"); public static final FilterCreaturePermanent FILTER_PERMANENT_CREATURE_GOBLINS = new FilterCreaturePermanent(SubType.GOBLIN, "Goblin creatures"); public static final FilterCreaturePermanent FILTER_PERMANENT_CREATURE_SLIVERS = new FilterCreaturePermanent(SubType.SLIVER, "all Sliver creatures"); public static final FilterPlaneswalkerPermanent FILTER_PERMANENT_PLANESWALKER = new FilterPlaneswalkerPermanent(); @@ -108,6 +109,8 @@ public final class StaticFilters { FILTER_ATTACKING_CREATURES.add(new AttackingPredicate()); + FILTER_PERMANENT_CREATURES_CONTROLLED.add(new ControllerPredicate(TargetController.YOU)); + FILTER_PERMANENT_ARTIFACT_OR_CREATURE.add(Predicates.or( new CardTypePredicate(CardType.ARTIFACT), new CardTypePredicate(CardType.CREATURE) diff --git a/Mage/src/main/java/mage/watchers/WatcherUtils.java b/Mage/src/main/java/mage/watchers/WatcherUtils.java new file mode 100644 index 00000000000..ec717a98221 --- /dev/null +++ b/Mage/src/main/java/mage/watchers/WatcherUtils.java @@ -0,0 +1,46 @@ +/* + * 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.watchers; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.game.Game; +import org.apache.log4j.Logger; + +/** + * + * @author LevelX2 + */ +public final class WatcherUtils { + + public static void logMissingWatcher(Game game, Ability source, Class watcherClass, Class usingClass) { + MageObject sourceObject = source.getSourceObject(game); + Logger.getLogger(usingClass).error("Needed watcher is not started " + watcherClass.getSimpleName() + + " - " + (sourceObject == null ? " no source object" : sourceObject.getName())); + } +} diff --git a/Mage/src/main/java/mage/watchers/common/PlayerLostLifeWatcher.java b/Mage/src/main/java/mage/watchers/common/PlayerLostLifeWatcher.java index 462f2db4083..9dce3c3c5ba 100644 --- a/Mage/src/main/java/mage/watchers/common/PlayerLostLifeWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/PlayerLostLifeWatcher.java @@ -38,7 +38,7 @@ import mage.watchers.Watcher; /* * Counts amount of life lost current or last turn by players. - * This watcher is always added in gameImpl.init + * This watcher is automatically started in gameImpl.init for each game * * @author LevelX2 */ diff --git a/Utils/keywords.txt b/Utils/keywords.txt index 875ab52c008..f78722fbbbf 100644 --- a/Utils/keywords.txt +++ b/Utils/keywords.txt @@ -1,5 +1,6 @@ Afflict|number| Annihilator|number| +Ascend|new| Basic landcycling|cost| Battle cry|new| Bestow|card, manaString| From d5fa7af27037c386221f33354d525f9c63aa5874 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Tue, 2 Jan 2018 18:07:58 +0100 Subject: [PATCH 45/45] [RIX] Minor text change for "The Immortal Sun". --- Mage.Sets/src/mage/cards/t/TheImmortalSun.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/cards/t/TheImmortalSun.java b/Mage.Sets/src/mage/cards/t/TheImmortalSun.java index 96d723723e8..f8505beebff 100644 --- a/Mage.Sets/src/mage/cards/t/TheImmortalSun.java +++ b/Mage.Sets/src/mage/cards/t/TheImmortalSun.java @@ -62,7 +62,7 @@ public class TheImmortalSun extends CardImpl { this.addSuperType(SuperType.LEGENDARY); - // Players can't activate loyalty abilities of planeswalkers. + // Players can't activate planeswalkers' loyalty abilities. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new TheImmortalSunCantActivateEffect())); // At the beginning of your draw step, draw an additional card. this.addAbility(new BeginningOfDrawTriggeredAbility(new DrawCardSourceControllerEffect(1), TargetController.YOU, false)); @@ -86,7 +86,7 @@ class TheImmortalSunCantActivateEffect extends ContinuousRuleModifyingEffectImpl public TheImmortalSunCantActivateEffect() { super(Duration.WhileOnBattlefield, Outcome.Detriment); - staticText = "Players can't activate loyalty abilities of planeswalkers"; + staticText = "Players can't activate planeswalkers' loyalty abilities"; } public TheImmortalSunCantActivateEffect(final TheImmortalSunCantActivateEffect effect) {