From 6a55c87eefb1f8913b42db2e91c4564dad9a3136 Mon Sep 17 00:00:00 2001 From: Jeff Date: Tue, 18 Apr 2017 15:12:08 -0500 Subject: [PATCH 01/22] - Fixed Cruel Reality. Bug #3162 --- Mage.Sets/src/mage/cards/c/CruelReality.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Mage.Sets/src/mage/cards/c/CruelReality.java b/Mage.Sets/src/mage/cards/c/CruelReality.java index a5c35dc040..66be70bbb4 100644 --- a/Mage.Sets/src/mage/cards/c/CruelReality.java +++ b/Mage.Sets/src/mage/cards/c/CruelReality.java @@ -147,13 +147,12 @@ class CruelRealityEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); if (cursedPlayer != null && controller != null) { - if (cursedPlayer.chooseUse(outcome, "Sacrifice a creature or planeswalker?", source, game)) { - FilterControlledPermanent filter = new FilterControlledPermanent(); + FilterControlledPermanent filter = new FilterControlledPermanent("creature or planeswalker"); filter.add(Predicates.or( new CardTypePredicate(CardType.CREATURE), new CardTypePredicate(CardType.PLANESWALKER))); TargetPermanent target = new TargetPermanent(filter); - if (cursedPlayer.choose(outcome, target, source.getId(), game)) { + if (cursedPlayer.choose(Outcome.Sacrifice, target, source.getId(), game)) { Permanent objectToBeSacrificed = game.getPermanent(target.getFirstTarget()); if (objectToBeSacrificed != null) { if (objectToBeSacrificed.sacrifice(source.getId(), game)) { @@ -161,7 +160,6 @@ class CruelRealityEffect extends OneShotEffect { } } } - } cursedPlayer.loseLife(5, game, false); } return false; From f5fe2bd133607bd24e0358463bf493a416565816 Mon Sep 17 00:00:00 2001 From: magenoxx Date: Tue, 18 Apr 2017 23:28:42 +0300 Subject: [PATCH 02/22] Issue#3148: added reproducing test --- .../cost/modification/FluctuatorTest.java | 112 +++++++++++++++++ .../mage/abilities/costs/WrapperCost.java | 41 ++++++ .../abilities/costs/common/CyclingCost.java | 119 ++++++++++++++++++ .../abilities/keyword/CyclingAbility.java | 75 +---------- 4 files changed, 273 insertions(+), 74 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/FluctuatorTest.java create mode 100644 Mage/src/main/java/mage/abilities/costs/WrapperCost.java create mode 100644 Mage/src/main/java/mage/abilities/costs/common/CyclingCost.java diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/FluctuatorTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/FluctuatorTest.java new file mode 100644 index 0000000000..197a7c5629 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/FluctuatorTest.java @@ -0,0 +1,112 @@ +/* + * 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 org.mage.test.cards.cost.modification; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author noxx + */ +public class FluctuatorTest extends CardTestPlayerBase { + + /** + * Fluctuator makes 'Akroma's Vengeance' cyclic cost reduced to {1} + * Test it with one Plains on battlefield. + */ + @Test + public void testFluctuatorReducedBy2() { + + // Destroy all artifacts, creatures, and enchantments. + // Cycling ({3}, Discard this card: Draw a card.) + addCard(Zone.HAND, playerA, "Akroma's Vengeance"); + + // Cycling abilities you activate cost you up to {2} less to activate. + addCard(Zone.BATTLEFIELD, playerA, "Fluctuator"); + + // One mana should be enough + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cycling"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Akroma's Vengeance", 1); + assertHandCount(playerA, 1); + } + + /** + * Fluctuator makes 'Akroma's Vengeance' cyclic cost reduced to {1} + * + * Make sure it wasn't reduced more than by two. + */ + @Test + public void testFluctuatorReducedNotBy3() { + + // Destroy all artifacts, creatures, and enchantments. + // Cycling ({3}, Discard this card: Draw a card.) + addCard(Zone.HAND, playerA, "Akroma's Vengeance"); + + // Cycling abilities you activate cost you up to {2} less to activate. + addCard(Zone.BATTLEFIELD, playerA, "Fluctuator"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cycling"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Akroma's Vengeance", 0); + assertHandCount(playerA, 1); + } + + /** + * Test 2 Fluctuators reduce cycling cost up to 4. + * + */ + @Test + public void testTwoFluctuatorsReduceBy4() { + + // Destroy all artifacts, creatures, and enchantments. + // Cycling ({3}, Discard this card: Draw a card.) + addCard(Zone.HAND, playerA, "Akroma's Vengeance"); + + // Cycling abilities you activate cost you up to {2} less to activate. + addCard(Zone.BATTLEFIELD, playerA, "Fluctuator", 2); // 2 copies + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cycling"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Akroma's Vengeance", 1); + assertHandCount(playerA, 1); + } +} diff --git a/Mage/src/main/java/mage/abilities/costs/WrapperCost.java b/Mage/src/main/java/mage/abilities/costs/WrapperCost.java new file mode 100644 index 0000000000..c9714a8acf --- /dev/null +++ b/Mage/src/main/java/mage/abilities/costs/WrapperCost.java @@ -0,0 +1,41 @@ +/* + * Copyright 2017 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.costs; + +import java.io.Serializable; + +/** + * Some Cost act like wrappers hiding real costs inside + * + * @author noxx + */ +public interface WrapperCost extends Serializable { + + Cost getOriginalCost(); + +} diff --git a/Mage/src/main/java/mage/abilities/costs/common/CyclingCost.java b/Mage/src/main/java/mage/abilities/costs/common/CyclingCost.java new file mode 100644 index 0000000000..ebcbdfd00d --- /dev/null +++ b/Mage/src/main/java/mage/abilities/costs/common/CyclingCost.java @@ -0,0 +1,119 @@ +/* + * Copyright 2017 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.costs.common; + +import mage.abilities.Ability; +import mage.abilities.costs.Cost; +import mage.abilities.costs.WrapperCost; +import mage.game.Game; +import mage.game.events.CostEvent; +import mage.game.events.GameEvent; +import mage.target.Targets; + +import java.util.UUID; + +/** + * Cycling Cost to interact with cards like 'New Perspectives' + */ +public class CyclingCost implements Cost, WrapperCost { + + protected Cost cost; + + public CyclingCost(Cost cost) { + this.cost = cost; + } + + public CyclingCost(final CyclingCost cost) { + this.cost = cost.cost.copy(); + } + + @Override + public boolean pay(Ability ability, Game game, UUID sourceId, UUID controllerId, boolean noMana) { + return pay(ability, game, sourceId, controllerId, noMana, cost); + } + + @Override + public boolean canPay(Ability ability, UUID sourceId, UUID controllerId, Game game) { + CostEvent costEvent = new CostEvent(GameEvent.EventType.CAN_PAY_CYCLE_COST, sourceId, sourceId, controllerId, cost); + game.replaceEvent(costEvent); + return cost.canPay(ability, sourceId, controllerId, game) || costEvent.getCost().canPay(ability, sourceId, controllerId, game); + } + + @Override + public boolean pay(Ability ability, Game game, UUID sourceId, UUID controllerId, boolean noMana, Cost costToPay) { + CostEvent costEvent = new CostEvent(GameEvent.EventType.PAY_CYCLE_COST, sourceId, sourceId, controllerId, cost); + game.replaceEvent(costEvent); + cost = costEvent.getCost(); + return cost.pay(ability, game, sourceId, controllerId, noMana, cost); + } + + @Override + public String getText() { + return cost.getText(); + } + + @Override + public void setText(String text) { + cost.setText(text); + } + + @Override + public Targets getTargets() { + return cost.getTargets(); + } + + @Override + public boolean isPaid() { + return cost.isPaid(); + } + + @Override + public void clearPaid() { + cost.clearPaid(); + } + + @Override + public void setPaid() { + cost.setPaid(); + } + + @Override + public UUID getId() { + return cost.getId(); + } + + @Override + public Cost copy() { + return new CyclingCost(this); + } + + @Override + public Cost getOriginalCost() { + return this.cost; + } +} \ No newline at end of file diff --git a/Mage/src/main/java/mage/abilities/keyword/CyclingAbility.java b/Mage/src/main/java/mage/abilities/keyword/CyclingAbility.java index 09f1c72d45..dba43008be 100644 --- a/Mage/src/main/java/mage/abilities/keyword/CyclingAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/CyclingAbility.java @@ -31,6 +31,7 @@ import java.util.UUID; import mage.abilities.Ability; import mage.abilities.ActivatedAbilityImpl; import mage.abilities.costs.Cost; +import mage.abilities.costs.common.CyclingCost; import mage.abilities.costs.common.DiscardSourceCost; import mage.abilities.costs.mana.ManaCost; import mage.abilities.effects.common.DrawCardSourceControllerEffect; @@ -90,77 +91,3 @@ public class CyclingAbility extends ActivatedAbilityImpl { } } - -class CyclingCost implements Cost { - - protected Cost cost; - - public CyclingCost(Cost cost) { - this.cost = cost; - } - - public CyclingCost(final CyclingCost cost) { - this.cost = cost.cost.copy(); - } - - @Override - public boolean pay(Ability ability, Game game, UUID sourceId, UUID controllerId, boolean noMana) { - return pay(ability, game, sourceId, controllerId, noMana, cost); - } - - @Override - public boolean canPay(Ability ability, UUID sourceId, UUID controllerId, Game game) { - CostEvent costEvent = new CostEvent(GameEvent.EventType.CAN_PAY_CYCLE_COST, sourceId, sourceId, controllerId, cost); - game.replaceEvent(costEvent); - return cost.canPay(ability, sourceId, controllerId, game) || costEvent.getCost().canPay(ability, sourceId, controllerId, game); - } - - @Override - public boolean pay(Ability ability, Game game, UUID sourceId, UUID controllerId, boolean noMana, Cost costToPay) { - CostEvent costEvent = new CostEvent(GameEvent.EventType.PAY_CYCLE_COST, sourceId, sourceId, controllerId, cost); - game.replaceEvent(costEvent); - cost = costEvent.getCost(); - return cost.pay(ability, game, sourceId, controllerId, noMana, cost); - } - - @Override - public String getText() { - return cost.getText(); - } - - @Override - public void setText(String text) { - cost.setText(text); - } - - @Override - public Targets getTargets() { - return cost.getTargets(); - } - - @Override - public boolean isPaid() { - return cost.isPaid(); - } - - @Override - public void clearPaid() { - cost.clearPaid(); - } - - @Override - public void setPaid() { - cost.setPaid(); - } - - @Override - public UUID getId() { - return cost.getId(); - } - - @Override - public Cost copy() { - return new CyclingCost(this); - } - -} From 625efd8150c8535c16acab03e2317b066d3ffe95 Mon Sep 17 00:00:00 2001 From: Jeff Date: Tue, 18 Apr 2017 15:34:05 -0500 Subject: [PATCH 03/22] - Fixed Approach of the Second Sun. Bug #3122 --- Mage.Sets/src/mage/cards/a/ApproachOfTheSecondSun.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Mage.Sets/src/mage/cards/a/ApproachOfTheSecondSun.java b/Mage.Sets/src/mage/cards/a/ApproachOfTheSecondSun.java index 1778030482..b3f68411e6 100644 --- a/Mage.Sets/src/mage/cards/a/ApproachOfTheSecondSun.java +++ b/Mage.Sets/src/mage/cards/a/ApproachOfTheSecondSun.java @@ -81,8 +81,7 @@ class ApproachOfTheSecondSunEffect extends OneShotEffect { } // Is the library now empty, thus the rise is on the bottom (for the message to the players)? - boolean isOnBottom = !controller.getLibrary().hasCards(); - + boolean isOnBottom = controller.getLibrary().size() < 6; // Put this card (if the ability came from an ApproachOfTheSecondSun spell card) on top spellCard.moveToZone(Zone.LIBRARY, source.getSourceId(), game, true); @@ -108,7 +107,7 @@ class ApproachOfTheSecondSunEffect extends OneShotEffect { class ApproachOfTheSecondSunWatcher extends Watcher { - private Map approachesCast = new HashMap<>(); + private Map approachesCast = new HashMap<>(); public ApproachOfTheSecondSunWatcher() { super(ApproachOfTheSecondSunWatcher.class.getName(), WatcherScope.GAME); From cf90bb4d36d42b8e75e6532007ddb62d1a216b4b Mon Sep 17 00:00:00 2001 From: Jeff Date: Tue, 18 Apr 2017 15:44:41 -0500 Subject: [PATCH 04/22] - Text change for Approach of the Second Sun. --- Mage.Sets/src/mage/cards/a/ApproachOfTheSecondSun.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/a/ApproachOfTheSecondSun.java b/Mage.Sets/src/mage/cards/a/ApproachOfTheSecondSun.java index b3f68411e6..d6c22bc413 100644 --- a/Mage.Sets/src/mage/cards/a/ApproachOfTheSecondSun.java +++ b/Mage.Sets/src/mage/cards/a/ApproachOfTheSecondSun.java @@ -95,7 +95,7 @@ class ApproachOfTheSecondSunEffect extends OneShotEffect { if (isOnBottom) { game.informPlayers(controller.getLogName() + " puts " + spell.getLogName() + " on the bottom of his or her library."); } else { - game.informPlayers(controller.getLogName() + " puts " + spell.getLogName() + " into his or her library 6th from the top."); + game.informPlayers(controller.getLogName() + " puts " + spell.getLogName() + " into his or her library 7th from the top."); } } } From a4afdb8d3f8d96933df2d4563ce81f8545d15414 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Tue, 18 Apr 2017 23:35:45 +0200 Subject: [PATCH 05/22] Removed some not needed error log messages. --- .../src/main/java/mage/server/UserManager.java | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/Mage.Server/src/main/java/mage/server/UserManager.java b/Mage.Server/src/main/java/mage/server/UserManager.java index 0aaab44e74..f33b3fe054 100644 --- a/Mage.Server/src/main/java/mage/server/UserManager.java +++ b/Mage.Server/src/main/java/mage/server/UserManager.java @@ -27,15 +27,14 @@ */ package mage.server; +import java.util.*; +import java.util.concurrent.*; import mage.server.User.UserState; import mage.server.record.UserStats; import mage.server.record.UserStatsRepository; import mage.server.util.ThreadExecutor; import org.apache.log4j.Logger; -import java.util.*; -import java.util.concurrent.*; - /** * manages users - if a user is disconnected and 10 minutes have passed with no * activity the user is removed @@ -68,8 +67,7 @@ public enum UserManager { public Optional getUser(UUID userId) { if (!users.containsKey(userId)) { - LOGGER.error(String.format("User with id %s could not be found", userId)); - + LOGGER.trace(String.format("User with id %s could not be found", userId)); return Optional.empty(); } else { return Optional.of(users.get(userId)); @@ -82,10 +80,7 @@ public enum UserManager { if (u.isPresent()) { return u; } else { - - LOGGER.error("User with name " + userName + " could not be found"); return Optional.empty(); - } } @@ -123,8 +118,8 @@ public enum UserManager { public void removeUser(final UUID userId, final DisconnectReason reason) { if (userId != null) { - getUser(userId).ifPresent(user -> - USER_EXECUTOR.execute( + getUser(userId).ifPresent(user + -> USER_EXECUTOR.execute( () -> { try { LOGGER.info("USER REMOVE - " + user.getName() + " (" + reason.toString() + ") userId: " + userId + " [" + user.getGameInfo() + ']'); From e475ad9df7c09300310e62f72fe5f2c3a2082dd9 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Tue, 18 Apr 2017 23:36:06 +0200 Subject: [PATCH 06/22] * Abeyance - Fixed possible null pointer exception. --- Mage.Sets/src/mage/cards/a/Abeyance.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Mage.Sets/src/mage/cards/a/Abeyance.java b/Mage.Sets/src/mage/cards/a/Abeyance.java index a2a4402eb1..d59e287112 100644 --- a/Mage.Sets/src/mage/cards/a/Abeyance.java +++ b/Mage.Sets/src/mage/cards/a/Abeyance.java @@ -27,6 +27,8 @@ */ package mage.cards.a; +import java.util.Optional; +import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; @@ -41,9 +43,6 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.target.TargetPlayer; -import java.util.Optional; -import java.util.UUID; - /** * * @author fireshoes @@ -51,7 +50,7 @@ import java.util.UUID; public class Abeyance extends CardImpl { public Abeyance(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{1}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{W}"); // Until end of turn, target player can't cast instant or sorcery spells, and that player can't activate abilities that aren't mana abilities. this.getSpellAbility().addEffect(new AbeyanceEffect()); @@ -103,7 +102,7 @@ class AbeyanceEffect extends ContinuousRuleModifyingEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { - if (event.getPlayerId().equals(source.getFirstTarget())) { + if (source.getFirstTarget() != null && source.getFirstTarget().equals(event.getPlayerId())) { MageObject object = game.getObject(event.getSourceId()); if (event.getType() == GameEvent.EventType.CAST_SPELL) { if (object.isInstant() || object.isSorcery()) { From 91342cc350c7fe8a7f0e42d4969c64d72862a0ac Mon Sep 17 00:00:00 2001 From: Jeff Date: Tue, 18 Apr 2017 16:48:48 -0500 Subject: [PATCH 07/22] - Fixed the DiscardTest. --- .../mage/test/cards/abilities/keywords/DiscardTest.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/DiscardTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/DiscardTest.java index 5ebbe02942..0b9d330d1d 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/DiscardTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/DiscardTest.java @@ -48,18 +48,17 @@ public class DiscardTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); addCard(Zone.HAND, playerA, "Tranquil Thicket"); - addCard(Zone.BATTLEFIELD, playerB, "Rest in Peace", 1); - activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cycling {G} ({G},Discard {this}: Draw a card.)"); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cycling {G}"); //cycling ability setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); assertLife(playerA, 20); assertLife(playerB, 20); - assertHandCount(playerA, "Tranquil Thicket", 0); - assertExileCount("Tranquil Thicket", 1); + assertExileCount("Tranquil Thicket", 1); //exiled by Rest in Peace + assertHandCount(playerA, "Tranquil Thicket", 0); //should be exiled assertHandCount(playerA, 1); // the card drawn by Cycling } From d7671bf0b1033deedb803e1c448efe7db526509b Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Tue, 18 Apr 2017 23:59:06 +0200 Subject: [PATCH 08/22] * Time to Reflect - Fixed possible null pointer exception. --- Mage.Sets/src/mage/cards/t/TimeToReflect.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/t/TimeToReflect.java b/Mage.Sets/src/mage/cards/t/TimeToReflect.java index 6c5cb7513d..4c23e0f9fc 100644 --- a/Mage.Sets/src/mage/cards/t/TimeToReflect.java +++ b/Mage.Sets/src/mage/cards/t/TimeToReflect.java @@ -45,6 +45,7 @@ import mage.filter.predicate.Predicates; import mage.filter.predicate.permanent.PermanentIdPredicate; 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.watchers.Watcher; @@ -73,7 +74,10 @@ public class TimeToReflect extends CardImpl { BlockedOrWasBlockedByAZombieWatcher watcher = (BlockedOrWasBlockedByAZombieWatcher) game.getState().getWatchers().get("BlockedOrWasBlockedByAZombieWatcher"); if (watcher != null) { for (MageObjectReference mor : watcher.getBlockedThisTurnCreatures()) { - creaturesThatBlockedOrWereBlockedByAZombie.add(new PermanentIdPredicate(mor.getPermanent(game).getId())); + Permanent permanent = mor.getPermanent(game); + if (permanent != null) { + creaturesThatBlockedOrWereBlockedByAZombie.add(new PermanentIdPredicate(permanent.getId())); + } } } filter.add(Predicates.or(creaturesThatBlockedOrWereBlockedByAZombie)); From 01153965c4d275f3a6981b9ec5ad49333510347a Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Wed, 19 Apr 2017 00:07:34 +0200 Subject: [PATCH 09/22] * Mimic Vat - Fixed possible null pointer exception. --- Mage.Sets/src/mage/cards/m/MimicVat.java | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/Mage.Sets/src/mage/cards/m/MimicVat.java b/Mage.Sets/src/mage/cards/m/MimicVat.java index e5873b7201..21e3ae8a9c 100644 --- a/Mage.Sets/src/mage/cards/m/MimicVat.java +++ b/Mage.Sets/src/mage/cards/m/MimicVat.java @@ -27,6 +27,8 @@ */ package mage.cards.m; +import java.util.HashSet; +import java.util.Set; import java.util.UUID; import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; @@ -151,17 +153,20 @@ class MimicVatEffect extends OneShotEffect { return false; } // return older cards to graveyard - for (UUID imprinted : permanent.getImprinted()) { - Card card = game.getCard(imprinted); - controller.moveCards(card, Zone.GRAVEYARD, source, game); + Set toGraveyard = new HashSet<>(); + for (UUID imprintedId : permanent.getImprinted()) { + Card card = game.getCard(imprintedId); + if (card != null) { + toGraveyard.add(card); + } } + controller.moveCards(toGraveyard, Zone.GRAVEYARD, source, game); permanent.clearImprinted(game); // Imprint a new one - UUID target = targetPointer.getFirst(game, source); - if (target != null) { - Card card = game.getCard(target); - card.moveToExile(getId(), "Mimic Vat (Imprint)", source.getSourceId(), game); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); + if (card != null) { + controller.moveCardsToExile(card, source, game, true, source.getSourceId(), permanent.getName() + " (Imprint)"); permanent.imprint(card.getId(), game); } From e5e614c34116504daeea614148f622da602a2b5c Mon Sep 17 00:00:00 2001 From: fireshoes Date: Tue, 18 Apr 2017 19:07:03 -0500 Subject: [PATCH 10/22] [AKH] Added common tapland cycle shown in the Gatherer --- Mage.Sets/src/mage/sets/Amonkhet.java | 728 +++++++++++++------------- 1 file changed, 368 insertions(+), 360 deletions(-) diff --git a/Mage.Sets/src/mage/sets/Amonkhet.java b/Mage.Sets/src/mage/sets/Amonkhet.java index a8eb00ed63..e791c75315 100644 --- a/Mage.Sets/src/mage/sets/Amonkhet.java +++ b/Mage.Sets/src/mage/sets/Amonkhet.java @@ -1,360 +1,368 @@ -/* - * Copyright 2011 BetaSteward_at_googlemail.com. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are - * permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation are those of the - * authors and should not be interpreted as representing official policies, either expressed - * or implied, of BetaSteward_at_googlemail.com. - */ -package mage.sets; - -import java.util.ArrayList; -import java.util.List; -import mage.cards.CardGraphicInfo; -import mage.cards.ExpansionSet; -import mage.cards.repository.CardCriteria; -import mage.cards.repository.CardInfo; -import mage.cards.repository.CardRepository; -import mage.constants.Rarity; -import mage.constants.SetType; - -/** - * - * @author fireshoes - */ -public class Amonkhet extends ExpansionSet { - - private static final Amonkhet instance = new Amonkhet(); - - public static Amonkhet getInstance() { - return instance; - } - - protected final List savedSpecialLand = new ArrayList<>(); - - private Amonkhet() { - super("Amonkhet", "AKH", ExpansionSet.buildDate(2017, 4, 28), SetType.EXPANSION); - this.blockName = "Amonkhet"; - this.hasBoosters = true; - this.hasBasicLands = true; - this.numBoosterLands = 1; - this.numBoosterCommon = 10; - this.numBoosterUncommon = 3; - this.numBoosterRare = 1; - this.ratioBoosterMythic = 8; - this.maxCardNumberInBooster = 269; - this.ratioBoosterSpecialLand = 144; - - cards.add(new SetCardInfo("Ahn-Crop Champion", 194, Rarity.UNCOMMON, mage.cards.a.AhnCropChampion.class)); - cards.add(new SetCardInfo("Ahn-Crop Crasher", 117, Rarity.UNCOMMON, mage.cards.a.AhnCropCrasher.class)); - cards.add(new SetCardInfo("Ancient Crab", 40, Rarity.COMMON, mage.cards.a.AncientCrab.class)); - cards.add(new SetCardInfo("Angel of Sanctions", 1, Rarity.MYTHIC, mage.cards.a.AngelOfSanctions.class)); - cards.add(new SetCardInfo("Angler Drake", 41, Rarity.UNCOMMON, mage.cards.a.AnglerDrake.class)); - cards.add(new SetCardInfo("Anointed Procession", 2, Rarity.RARE, mage.cards.a.AnointedProcession.class)); - cards.add(new SetCardInfo("Anointer Priest", 3, Rarity.COMMON, mage.cards.a.AnointerPriest.class)); - cards.add(new SetCardInfo("Approach of the Second Sun", 4, Rarity.RARE, mage.cards.a.ApproachOfTheSecondSun.class)); - cards.add(new SetCardInfo("Archfiend of Ifnir", 78, Rarity.RARE, mage.cards.a.ArchfiendOfIfnir.class)); - cards.add(new SetCardInfo("As Foretold", 42, Rarity.MYTHIC, mage.cards.a.AsForetold.class)); - cards.add(new SetCardInfo("Aven Initiate", 43, Rarity.COMMON, mage.cards.a.AvenInitiate.class)); - cards.add(new SetCardInfo("Aven Mindcensor", 5, Rarity.RARE, mage.cards.a.AvenMindcensor.class)); - cards.add(new SetCardInfo("Aven Wind Guide", 195, Rarity.UNCOMMON, mage.cards.a.AvenWindGuide.class)); - cards.add(new SetCardInfo("Baleful Ammit", 79, Rarity.UNCOMMON, mage.cards.b.BalefulAmmit.class)); - cards.add(new SetCardInfo("Battlefield Scavenger", 118, Rarity.UNCOMMON, mage.cards.b.BattlefieldScavenger.class)); - cards.add(new SetCardInfo("Benefaction of Rhonas", 156, Rarity.COMMON, mage.cards.b.BenefactionOfRhonas.class)); - cards.add(new SetCardInfo("Binding Mummy", 6, Rarity.COMMON, mage.cards.b.BindingMummy.class)); - cards.add(new SetCardInfo("Bitterblade Warrior", 157, Rarity.COMMON, mage.cards.b.BitterbladeWarrior.class)); - cards.add(new SetCardInfo("Blazing Volley", 119, Rarity.COMMON, mage.cards.b.BlazingVolley.class)); - cards.add(new SetCardInfo("Blighted Bat", 80, Rarity.COMMON, mage.cards.b.BlightedBat.class)); - cards.add(new SetCardInfo("Bloodlust Inciter", 120, Rarity.COMMON, mage.cards.b.BloodlustInciter.class)); - cards.add(new SetCardInfo("Bloodrage Brawler", 121, Rarity.UNCOMMON, mage.cards.b.BloodrageBrawler.class)); - cards.add(new SetCardInfo("Bone Picker", 81, Rarity.UNCOMMON, mage.cards.b.BonePicker.class)); - cards.add(new SetCardInfo("Bounty of the Luxa", 196, Rarity.RARE, mage.cards.b.BountyOfTheLuxa.class)); - cards.add(new SetCardInfo("Bontu's Monument", 225, Rarity.UNCOMMON, mage.cards.b.BontusMonument.class)); - cards.add(new SetCardInfo("Bontu the Glorified", 82, Rarity.MYTHIC, mage.cards.b.BontuTheGlorified.class)); - cards.add(new SetCardInfo("Brute Strength", 122, Rarity.COMMON, mage.cards.b.BruteStrength.class)); - cards.add(new SetCardInfo("By Force", 123, Rarity.UNCOMMON, mage.cards.b.ByForce.class)); - cards.add(new SetCardInfo("Cancel", 44, Rarity.COMMON, mage.cards.c.Cancel.class)); - cards.add(new SetCardInfo("Canyon Slough", 239, Rarity.RARE, mage.cards.c.CanyonSlough.class)); - cards.add(new SetCardInfo("Cartouche of Ambition", 83, Rarity.COMMON, mage.cards.c.CartoucheOfAmbition.class)); - cards.add(new SetCardInfo("Cartouche of Knowledge", 45, Rarity.COMMON, mage.cards.c.CartoucheOfKnowledge.class)); - cards.add(new SetCardInfo("Cartouche of Solidarity", 7, Rarity.COMMON, mage.cards.c.CartoucheOfSolidarity.class)); - cards.add(new SetCardInfo("Cartouche of Strength", 158, Rarity.COMMON, mage.cards.c.CartoucheOfStrength.class)); - cards.add(new SetCardInfo("Cartouche of Zeal", 124, Rarity.COMMON, mage.cards.c.CartoucheOfZeal.class)); - cards.add(new SetCardInfo("Cascading Cataracts", 240, Rarity.RARE, mage.cards.c.CascadingCataracts.class)); - cards.add(new SetCardInfo("Cast Out", 8, Rarity.UNCOMMON, mage.cards.c.CastOut.class)); - cards.add(new SetCardInfo("Censor", 46, Rarity.UNCOMMON, mage.cards.c.Censor.class)); - cards.add(new SetCardInfo("Champion of Rhonas", 159, Rarity.RARE, mage.cards.c.ChampionOfRhonas.class)); - cards.add(new SetCardInfo("Channeler Initiate", 160, Rarity.RARE, mage.cards.c.ChannelerInitiate.class)); - cards.add(new SetCardInfo("Colossapede", 161, Rarity.COMMON, mage.cards.c.Colossapede.class)); - cards.add(new SetCardInfo("Combat Celebrant", 125, Rarity.MYTHIC, mage.cards.c.CombatCelebrant.class)); - cards.add(new SetCardInfo("Commit // Memory", 211, Rarity.RARE, mage.cards.c.CommitMemory.class)); - cards.add(new SetCardInfo("Companion of the Trials", 271, Rarity.UNCOMMON, mage.cards.c.CompanionOfTheTrials.class)); - cards.add(new SetCardInfo("Compelling Argument", 47, Rarity.COMMON, mage.cards.c.CompellingArgument.class)); - cards.add(new SetCardInfo("Compulsory Rest", 9, Rarity.COMMON, mage.cards.c.CompulsoryRest.class)); - cards.add(new SetCardInfo("Consuming Fervor", 126, Rarity.UNCOMMON, mage.cards.c.ConsumingFervor.class)); - cards.add(new SetCardInfo("Cradle of the Accursed", 241, Rarity.COMMON, mage.cards.c.CradleOfTheAccursed.class)); - cards.add(new SetCardInfo("Crocodile of the Crossing", 162, Rarity.UNCOMMON, mage.cards.c.CrocodileOfTheCrossing.class)); - cards.add(new SetCardInfo("Cryptic Serpent", 48, Rarity.UNCOMMON, mage.cards.c.CrypticSerpent.class)); - cards.add(new SetCardInfo("Cruel Reality", 84, Rarity.MYTHIC, mage.cards.c.CruelReality.class)); - cards.add(new SetCardInfo("Curator of Mysteries", 49, Rarity.RARE, mage.cards.c.CuratorOfMysteries.class)); - cards.add(new SetCardInfo("Cursed Minotaur", 85, Rarity.COMMON, mage.cards.c.CursedMinotaur.class)); - cards.add(new SetCardInfo("Cut // Ribbons", 223, Rarity.RARE, mage.cards.c.CutRibbons.class)); - cards.add(new SetCardInfo("Decimator Beetle", 197, Rarity.UNCOMMON, mage.cards.d.DecimatorBeetle.class)); - cards.add(new SetCardInfo("Decision Paralysis", 50, Rarity.COMMON, mage.cards.d.DecisionParalysis.class)); - cards.add(new SetCardInfo("Deem Worthy", 127, Rarity.UNCOMMON, mage.cards.d.DeemWorthy.class)); - cards.add(new SetCardInfo("Defiant Greatmaw", 163, Rarity.UNCOMMON, mage.cards.d.DefiantGreatmaw.class)); - cards.add(new SetCardInfo("Desert Cerodon", 128, Rarity.COMMON, mage.cards.d.DesertCerodon.class)); - cards.add(new SetCardInfo("Desiccated Naga", 276, Rarity.UNCOMMON, mage.cards.d.DesiccatedNaga.class)); - cards.add(new SetCardInfo("Destined // Lead", 217, Rarity.UNCOMMON, mage.cards.d.DestinedLead.class)); - cards.add(new SetCardInfo("Devoted Crop-Mate", 10, Rarity.UNCOMMON, mage.cards.d.DevotedCropMate.class)); - cards.add(new SetCardInfo("Dispossess", 86, Rarity.RARE, mage.cards.d.Dispossess.class)); - cards.add(new SetCardInfo("Dissenter's Deliverance", 164, Rarity.COMMON, mage.cards.d.DissentersDeliverance.class)); - cards.add(new SetCardInfo("Djeru's Resolve", 11, Rarity.COMMON, mage.cards.d.DjerusResolve.class)); - cards.add(new SetCardInfo("Doomed Dissenter", 87, Rarity.COMMON, mage.cards.d.DoomedDissenter.class)); - cards.add(new SetCardInfo("Drake Haven", 51, Rarity.RARE, mage.cards.d.DrakeHaven.class)); - cards.add(new SetCardInfo("Dread Wanderer", 88, Rarity.RARE, mage.cards.d.DreadWanderer.class)); - cards.add(new SetCardInfo("Dune Beetle", 89, Rarity.COMMON, mage.cards.d.DuneBeetle.class)); - cards.add(new SetCardInfo("Dusk // Dawn", 210, Rarity.RARE, mage.cards.d.DuskDawn.class)); - cards.add(new SetCardInfo("Edifice of Authority", 226, Rarity.UNCOMMON, mage.cards.e.EdificeOfAuthority.class)); - cards.add(new SetCardInfo("Electrify", 129, Rarity.COMMON, mage.cards.e.Electrify.class)); - cards.add(new SetCardInfo("Embalmer's Tools", 227, Rarity.UNCOMMON, mage.cards.e.EmbalmersTools.class)); - cards.add(new SetCardInfo("Emberhorn Minotaur", 130, Rarity.COMMON, mage.cards.e.EmberhornMinotaur.class)); - cards.add(new SetCardInfo("Enigma Drake", 198, Rarity.UNCOMMON, mage.cards.e.EnigmaDrake.class)); - cards.add(new SetCardInfo("Essence Scatter", 52, Rarity.COMMON, mage.cards.e.EssenceScatter.class)); - cards.add(new SetCardInfo("Evolving Wilds", 242, Rarity.COMMON, mage.cards.e.EvolvingWilds.class)); - cards.add(new SetCardInfo("Exemplar of Strength", 165, Rarity.UNCOMMON, mage.cards.e.ExemplarOfStrength.class)); - cards.add(new SetCardInfo("Failure // Comply", 221, Rarity.RARE, mage.cards.f.FailureComply.class)); - cards.add(new SetCardInfo("Faith of the Devoted", 90, Rarity.UNCOMMON, mage.cards.f.FaithOfTheDevoted.class)); - cards.add(new SetCardInfo("Fan Bearer", 12, Rarity.COMMON, mage.cards.f.FanBearer.class)); - cards.add(new SetCardInfo("Festering Mummy", 91, Rarity.COMMON, mage.cards.f.FesteringMummy.class)); - cards.add(new SetCardInfo("Fetid Pools", 243, Rarity.RARE, mage.cards.f.FetidPools.class)); - cards.add(new SetCardInfo("Final Reward", 92, Rarity.COMMON, mage.cards.f.FinalReward.class)); - cards.add(new SetCardInfo("Flameblade Adept", 131, Rarity.UNCOMMON, mage.cards.f.FlamebladeAdept.class)); - cards.add(new SetCardInfo("Fling", 132, Rarity.COMMON, mage.cards.f.Fling.class)); - cards.add(new SetCardInfo("Floodwaters", 53, Rarity.COMMON, mage.cards.f.Floodwaters.class)); - cards.add(new SetCardInfo("Forest", 254, Rarity.LAND, mage.cards.basiclands.Forest.class, new CardGraphicInfo(null, true))); - cards.add(new SetCardInfo("Forest", 267, Rarity.LAND, mage.cards.basiclands.Forest.class, new CardGraphicInfo(null, true))); - cards.add(new SetCardInfo("Forest", 268, Rarity.LAND, mage.cards.basiclands.Forest.class, new CardGraphicInfo(null, true))); - cards.add(new SetCardInfo("Forest", 269, Rarity.LAND, mage.cards.basiclands.Forest.class, new CardGraphicInfo(null, true))); - cards.add(new SetCardInfo("Forsake the Worldly", 13, Rarity.COMMON, mage.cards.f.ForsakeTheWorldly.class)); - cards.add(new SetCardInfo("Foul Orchard", 279, Rarity.COMMON, mage.cards.f.FoulOrchard.class)); - cards.add(new SetCardInfo("Galestrike", 54, Rarity.UNCOMMON, mage.cards.g.Galestrike.class)); - cards.add(new SetCardInfo("Gate to the Afterlife", 228, Rarity.UNCOMMON, mage.cards.g.GateToTheAfterlife.class)); - cards.add(new SetCardInfo("Giant Spider", 166, Rarity.COMMON, mage.cards.g.GiantSpider.class)); - cards.add(new SetCardInfo("Gideon of the Trials", 14, Rarity.MYTHIC, mage.cards.g.GideonOfTheTrials.class)); - cards.add(new SetCardInfo("Gideon's Intervention", 15, Rarity.RARE, mage.cards.g.GideonsIntervention.class)); - cards.add(new SetCardInfo("Gideon's Resolve", 272, Rarity.RARE, mage.cards.g.GideonsResolve.class)); - cards.add(new SetCardInfo("Gideon, Martial Paragon", 270, Rarity.MYTHIC, mage.cards.g.GideonMartialParagon.class)); - cards.add(new SetCardInfo("Gift of Paradise", 167, Rarity.COMMON, mage.cards.g.GiftOfParadise.class)); - cards.add(new SetCardInfo("Glorious End", 133, Rarity.MYTHIC, mage.cards.g.GloriousEnd.class)); - cards.add(new SetCardInfo("Glory-Bound Initiate", 16, Rarity.RARE, mage.cards.g.GloryBoundInitiate.class)); - cards.add(new SetCardInfo("Glorybringer", 134, Rarity.RARE, mage.cards.g.Glorybringer.class)); - cards.add(new SetCardInfo("Glyph Keeper", 55, Rarity.RARE, mage.cards.g.GlyphKeeper.class)); - cards.add(new SetCardInfo("Graceful Cat", 273, Rarity.COMMON, mage.cards.g.GracefulCat.class)); - cards.add(new SetCardInfo("Grasping Dunes", 244, Rarity.UNCOMMON, mage.cards.g.GraspingDunes.class)); - cards.add(new SetCardInfo("Gravedigger", 93, Rarity.UNCOMMON, mage.cards.g.Gravedigger.class)); - cards.add(new SetCardInfo("Greater Sandwurm", 168, Rarity.COMMON, mage.cards.g.GreaterSandwurm.class)); - cards.add(new SetCardInfo("Grim Strider", 94, Rarity.UNCOMMON, mage.cards.g.GrimStrider.class)); - cards.add(new SetCardInfo("Gust Walker", 17, Rarity.COMMON, mage.cards.g.GustWalker.class)); - cards.add(new SetCardInfo("Hapatra's Mark", 169, Rarity.UNCOMMON, mage.cards.h.HapatrasMark.class)); - cards.add(new SetCardInfo("Hapatra, Vizier of Poisons", 199, Rarity.RARE, mage.cards.h.HapatraVizierOfPoisons.class)); - cards.add(new SetCardInfo("Harsh Mentor", 135, Rarity.RARE, mage.cards.h.HarshMentor.class)); - cards.add(new SetCardInfo("Harvest Season", 170, Rarity.RARE, mage.cards.h.HarvestSeason.class)); - cards.add(new SetCardInfo("Haze of Pollen", 171, Rarity.COMMON, mage.cards.h.HazeOfPollen.class)); - cards.add(new SetCardInfo("Hazoret the Fervent", 136, Rarity.MYTHIC, mage.cards.h.HazoretTheFervent.class)); - cards.add(new SetCardInfo("Hazoret's Favor", 137, Rarity.RARE, mage.cards.h.HazoretsFavor.class)); - cards.add(new SetCardInfo("Hazoret's Monument", 229, Rarity.UNCOMMON, mage.cards.h.HazoretsMonument.class)); - cards.add(new SetCardInfo("Heart-Piercer Manticore", 138, Rarity.RARE, mage.cards.h.HeartPiercerManticore.class)); - cards.add(new SetCardInfo("Heaven // Earth", 224, Rarity.RARE, mage.cards.h.HeavenEarth.class)); - cards.add(new SetCardInfo("Hekma Sentinels", 56, Rarity.COMMON, mage.cards.h.HekmaSentinels.class)); - cards.add(new SetCardInfo("Hieroglyphic Illumination", 57, Rarity.COMMON, mage.cards.h.HieroglyphicIllumination.class)); - cards.add(new SetCardInfo("Honed Khopesh", 230, Rarity.COMMON, mage.cards.h.HonedKhopesh.class)); - cards.add(new SetCardInfo("Honored Crop-Captain", 200, Rarity.UNCOMMON, mage.cards.h.HonoredCropCaptain.class)); - cards.add(new SetCardInfo("Honored Hydra", 172, Rarity.RARE, mage.cards.h.HonoredHydra.class)); - cards.add(new SetCardInfo("Hooded Brawler", 173, Rarity.COMMON, mage.cards.h.HoodedBrawler.class)); - cards.add(new SetCardInfo("Horror of the Broken Lands", 95, Rarity.COMMON, mage.cards.h.HorrorOfTheBrokenLands.class)); - cards.add(new SetCardInfo("Hyena Pack", 139, Rarity.COMMON, mage.cards.h.HyenaPack.class)); - cards.add(new SetCardInfo("Illusory Wrappings", 58, Rarity.COMMON, mage.cards.i.IllusoryWrappings.class)); - cards.add(new SetCardInfo("Impeccable Timing", 18, Rarity.COMMON, mage.cards.i.ImpeccableTiming.class)); - cards.add(new SetCardInfo("In Oketra's Name", 19, Rarity.COMMON, mage.cards.i.InOketrasName.class)); - cards.add(new SetCardInfo("Initiate's Companion", 174, Rarity.COMMON, mage.cards.i.InitiatesCompanion.class)); - cards.add(new SetCardInfo("Insult // Injury", 213, Rarity.RARE, mage.cards.i.InsultInjury.class)); - cards.add(new SetCardInfo("Irrigated Farmland", 245, Rarity.RARE, mage.cards.i.IrrigatedFarmland.class)); - cards.add(new SetCardInfo("Island", 251, Rarity.LAND, mage.cards.basiclands.Island.class, new CardGraphicInfo(null, true))); - cards.add(new SetCardInfo("Island", 258, Rarity.LAND, mage.cards.basiclands.Island.class, new CardGraphicInfo(null, true))); - cards.add(new SetCardInfo("Island", 259, Rarity.LAND, mage.cards.basiclands.Island.class, new CardGraphicInfo(null, true))); - cards.add(new SetCardInfo("Island", 260, Rarity.LAND, mage.cards.basiclands.Island.class, new CardGraphicInfo(null, true))); - cards.add(new SetCardInfo("Kefnet the Mindful", 59, Rarity.MYTHIC, mage.cards.k.KefnetTheMindful.class)); - cards.add(new SetCardInfo("Kefnet's Monument", 231, Rarity.UNCOMMON, mage.cards.k.KefnetsMonument.class)); - cards.add(new SetCardInfo("Khenra Charioteer", 201, Rarity.UNCOMMON, mage.cards.k.KhenraCharioteer.class)); - cards.add(new SetCardInfo("Labyrinth Guardian", 60, Rarity.UNCOMMON, mage.cards.l.LabyrinthGuardian.class)); - cards.add(new SetCardInfo("Lay Bare the Heart", 96, Rarity.UNCOMMON, mage.cards.l.LayBareTheHeart.class)); - cards.add(new SetCardInfo("Lay Claim", 61, Rarity.UNCOMMON, mage.cards.l.LayClaim.class)); - cards.add(new SetCardInfo("Liliana's Influence", 277, Rarity.RARE, mage.cards.l.LilianasInfluence.class)); - cards.add(new SetCardInfo("Liliana's Mastery", 98, Rarity.RARE, mage.cards.l.LilianasMastery.class)); - cards.add(new SetCardInfo("Liliana, Death Wielder", 275, Rarity.MYTHIC, mage.cards.l.LilianaDeathWielder.class)); - cards.add(new SetCardInfo("Liliana, Death's Majesty", 97, Rarity.MYTHIC, mage.cards.l.LilianaDeathsMajesty.class)); - cards.add(new SetCardInfo("Limits of Solidarity", 140, Rarity.UNCOMMON, mage.cards.l.LimitsOfSolidarity.class)); - cards.add(new SetCardInfo("Lord of the Accursed", 99, Rarity.UNCOMMON, mage.cards.l.LordOfTheAccursed.class)); - cards.add(new SetCardInfo("Luxa River Shrine", 232, Rarity.COMMON, mage.cards.l.LuxaRiverShrine.class)); - cards.add(new SetCardInfo("Magma Spray", 141, Rarity.COMMON, mage.cards.m.MagmaSpray.class)); - cards.add(new SetCardInfo("Manglehorn", 175, Rarity.UNCOMMON, mage.cards.m.Manglehorn.class)); - cards.add(new SetCardInfo("Manticore of the Gauntlet", 142, Rarity.COMMON, mage.cards.m.ManticoreOfTheGauntlet.class)); - cards.add(new SetCardInfo("Merciless Javelineer", 202, Rarity.UNCOMMON, mage.cards.m.MercilessJavelineer.class)); - cards.add(new SetCardInfo("Miasmic Mummy", 100, Rarity.COMMON, mage.cards.m.MiasmicMummy.class)); - cards.add(new SetCardInfo("Mighty Leap", 20, Rarity.COMMON, mage.cards.m.MightyLeap.class)); - cards.add(new SetCardInfo("Minotaur Sureshot", 143, Rarity.COMMON, mage.cards.m.MinotaurSureshot.class)); - cards.add(new SetCardInfo("Mountain", 253, Rarity.LAND, mage.cards.basiclands.Mountain.class, new CardGraphicInfo(null, true))); - cards.add(new SetCardInfo("Mountain", 264, Rarity.LAND, mage.cards.basiclands.Mountain.class, new CardGraphicInfo(null, true))); - cards.add(new SetCardInfo("Mountain", 265, Rarity.LAND, mage.cards.basiclands.Mountain.class, new CardGraphicInfo(null, true))); - cards.add(new SetCardInfo("Mountain", 266, Rarity.LAND, mage.cards.basiclands.Mountain.class, new CardGraphicInfo(null, true))); - cards.add(new SetCardInfo("Mouth // Feed", 214, Rarity.RARE, mage.cards.m.MouthFeed.class)); - cards.add(new SetCardInfo("Naga Oracle", 62, Rarity.COMMON, mage.cards.n.NagaOracle.class)); - cards.add(new SetCardInfo("Naga Vitalist", 176, Rarity.COMMON, mage.cards.n.NagaVitalist.class)); - cards.add(new SetCardInfo("Nef-Crop Entangler", 144, Rarity.COMMON, mage.cards.n.NefCropEntangler.class)); - cards.add(new SetCardInfo("Neheb, the Worthy", 203, Rarity.RARE, mage.cards.n.NehebTheWorthy.class)); - cards.add(new SetCardInfo("Nest of Scarabs", 101, Rarity.UNCOMMON, mage.cards.n.NestOfScarabs.class)); - cards.add(new SetCardInfo("Never // Return", 212, Rarity.RARE, mage.cards.n.NeverReturn.class)); - cards.add(new SetCardInfo("New Perspectives", 63, Rarity.RARE, mage.cards.n.NewPerspectives.class)); - cards.add(new SetCardInfo("Nimble-Blade Khenra", 145, Rarity.COMMON, mage.cards.n.NimbleBladeKhenra.class)); - cards.add(new SetCardInfo("Nissa, Steward of Elements", 204, Rarity.MYTHIC, mage.cards.n.NissaStewardOfElements.class)); - cards.add(new SetCardInfo("Oashra Cultivator", 177, Rarity.COMMON, mage.cards.o.OashraCultivator.class)); - cards.add(new SetCardInfo("Oketra the True", 21, Rarity.MYTHIC, mage.cards.o.OketraTheTrue.class)); - cards.add(new SetCardInfo("Oketra's Attendant", 22, Rarity.UNCOMMON, mage.cards.o.OketrasAttendant.class)); - cards.add(new SetCardInfo("Oketra's Monument", 233, Rarity.UNCOMMON, mage.cards.o.OketrasMonument.class)); - cards.add(new SetCardInfo("Onward // Victory", 218, Rarity.UNCOMMON, mage.cards.o.OnwardVictory.class)); - cards.add(new SetCardInfo("Open into Wonder", 64, Rarity.UNCOMMON, mage.cards.o.OpenIntoWonder.class)); - cards.add(new SetCardInfo("Oracle's Vault", 234, Rarity.RARE, mage.cards.o.OraclesVault.class)); - cards.add(new SetCardInfo("Ornery Kudu", 178, Rarity.COMMON, mage.cards.o.OrneryKudu.class)); - cards.add(new SetCardInfo("Painful Lesson", 102, Rarity.COMMON, mage.cards.p.PainfulLesson.class)); - cards.add(new SetCardInfo("Painted Bluffs", 246, Rarity.COMMON, mage.cards.p.PaintedBluffs.class)); - cards.add(new SetCardInfo("Pathmaker Initiate", 146, Rarity.COMMON, mage.cards.p.PathmakerInitiate.class)); - cards.add(new SetCardInfo("Pitiless Vizier", 103, Rarity.COMMON, mage.cards.p.PitilessVizier.class)); - cards.add(new SetCardInfo("Plague Belcher", 104, Rarity.RARE, mage.cards.p.PlagueBelcher.class)); - cards.add(new SetCardInfo("Pyramid of the Pantheon", 235, Rarity.RARE, mage.cards.p.PyramidOfThePantheon.class)); - cards.add(new SetCardInfo("Plains", 250, Rarity.LAND, mage.cards.basiclands.Plains.class, new CardGraphicInfo(null, true))); - cards.add(new SetCardInfo("Plains", 255, Rarity.LAND, mage.cards.basiclands.Plains.class, new CardGraphicInfo(null, true))); - cards.add(new SetCardInfo("Plains", 256, Rarity.LAND, mage.cards.basiclands.Plains.class, new CardGraphicInfo(null, true))); - cards.add(new SetCardInfo("Plains", 257, Rarity.LAND, mage.cards.basiclands.Plains.class, new CardGraphicInfo(null, true))); - cards.add(new SetCardInfo("Pouncing Cheetah", 179, Rarity.COMMON, mage.cards.p.PouncingCheetah.class)); - cards.add(new SetCardInfo("Prepare // Fight", 220, Rarity.RARE, mage.cards.p.PrepareFight.class)); - cards.add(new SetCardInfo("Protection of the Hekma", 23, Rarity.UNCOMMON, mage.cards.p.ProtectionOfTheHekma.class)); - cards.add(new SetCardInfo("Prowling Serpopard", 180, Rarity.RARE, mage.cards.p.ProwlingSerpopard.class)); - cards.add(new SetCardInfo("Pull from Tomorrow", 65, Rarity.RARE, mage.cards.p.PullFromTomorrow.class)); - cards.add(new SetCardInfo("Pursue Glory", 147, Rarity.COMMON, mage.cards.p.PursueGlory.class)); - cards.add(new SetCardInfo("Quarry Hauler", 181, Rarity.COMMON, mage.cards.q.QuarryHauler.class)); - cards.add(new SetCardInfo("Rags // Riches", 222, Rarity.RARE, mage.cards.r.RagsRiches.class)); - cards.add(new SetCardInfo("Reduce // Rubble", 216, Rarity.UNCOMMON, mage.cards.r.ReduceRubble.class)); - cards.add(new SetCardInfo("Regal Caracal", 24, Rarity.RARE, mage.cards.r.RegalCaracal.class)); - cards.add(new SetCardInfo("Renewed Faith", 25, Rarity.UNCOMMON, mage.cards.r.RenewedFaith.class)); - cards.add(new SetCardInfo("Rhet-Crop Spearmaster", 26, Rarity.COMMON, mage.cards.r.RhetCropSpearmaster.class)); - cards.add(new SetCardInfo("Rhonas's Monument", 236, Rarity.UNCOMMON, mage.cards.r.RhonassMonument.class)); - cards.add(new SetCardInfo("Rhonas the Indomitable", 182, Rarity.MYTHIC, mage.cards.r.RhonasTheIndomitable.class)); - cards.add(new SetCardInfo("River Serpent", 66, Rarity.COMMON, mage.cards.r.RiverSerpent.class)); - cards.add(new SetCardInfo("Ruthless Sniper", 105, Rarity.UNCOMMON, mage.cards.r.RuthlessSniper.class)); - cards.add(new SetCardInfo("Sacred Cat", 27, Rarity.COMMON, mage.cards.s.SacredCat.class)); - cards.add(new SetCardInfo("Sacred Excavation", 67, Rarity.UNCOMMON, mage.cards.s.SacredExcavation.class)); - cards.add(new SetCardInfo("Samut, Voice of Dissent", 205, Rarity.MYTHIC, mage.cards.s.SamutVoiceOfDissent.class)); - cards.add(new SetCardInfo("Sandwurm Convergence", 183, Rarity.RARE, mage.cards.s.SandwurmConvergence.class)); - cards.add(new SetCardInfo("Scaled Behemoth", 184, Rarity.UNCOMMON, mage.cards.s.ScaledBehemoth.class)); - cards.add(new SetCardInfo("Scarab Feast", 106, Rarity.COMMON, mage.cards.s.ScarabFeast.class)); - cards.add(new SetCardInfo("Scattered Groves", 247, Rarity.RARE, mage.cards.s.ScatteredGroves.class)); - cards.add(new SetCardInfo("Scribe of the Mindful", 68, Rarity.COMMON, mage.cards.s.ScribeOfTheMindful.class)); - cards.add(new SetCardInfo("Seeker of Insight", 69, Rarity.COMMON, mage.cards.s.SeekerOfInsight.class)); - cards.add(new SetCardInfo("Seraph of the Suns", 28, Rarity.UNCOMMON, mage.cards.s.SeraphOfTheSuns.class)); - cards.add(new SetCardInfo("Shadow of the Grave", 107, Rarity.RARE, mage.cards.s.ShadowOfTheGrave.class)); - cards.add(new SetCardInfo("Shadowstorm Vizier", 206, Rarity.UNCOMMON, mage.cards.s.ShadowstormVizier.class)); - cards.add(new SetCardInfo("Shed Weakness", 185, Rarity.COMMON, mage.cards.s.ShedWeakness.class)); - cards.add(new SetCardInfo("Shefet Monitor", 186, Rarity.UNCOMMON, mage.cards.s.ShefetMonitor.class)); - cards.add(new SetCardInfo("Sheltered Thicket", 248, Rarity.RARE, mage.cards.s.ShelteredThicket.class)); - cards.add(new SetCardInfo("Shimmerscale Drake", 70, Rarity.COMMON, mage.cards.s.ShimmerscaleDrake.class)); - cards.add(new SetCardInfo("Sixth Sense", 187, Rarity.UNCOMMON, mage.cards.s.SixthSense.class)); - cards.add(new SetCardInfo("Slither Blade", 71, Rarity.COMMON, mage.cards.s.SlitherBlade.class)); - cards.add(new SetCardInfo("Soul-Scar Mage", 148, Rarity.RARE, mage.cards.s.SoulScarMage.class)); - cards.add(new SetCardInfo("Soulstinger", 108, Rarity.COMMON, mage.cards.s.Soulstinger.class)); - cards.add(new SetCardInfo("Sparring Mummy", 29, Rarity.COMMON, mage.cards.s.SparringMummy.class)); - cards.add(new SetCardInfo("Spidery Grasp", 188, Rarity.COMMON, mage.cards.s.SpideryGrasp.class)); - cards.add(new SetCardInfo("Splendid Agony", 109, Rarity.COMMON, mage.cards.s.SplendidAgony.class)); - cards.add(new SetCardInfo("Spring // Mind", 219, Rarity.UNCOMMON, mage.cards.s.SpringMind.class)); - cards.add(new SetCardInfo("Start // Finish", 215, Rarity.UNCOMMON, mage.cards.s.StartFinish.class)); - cards.add(new SetCardInfo("Stinging Shot", 189, Rarity.COMMON, mage.cards.s.StingingShot.class)); - cards.add(new SetCardInfo("Stir the Sands", 110, Rarity.UNCOMMON, mage.cards.s.StirTheSands.class)); - cards.add(new SetCardInfo("Stone Quarry", 274, Rarity.COMMON, mage.cards.s.StoneQuarry.class)); - cards.add(new SetCardInfo("Sunscorched Desert", 249, Rarity.COMMON, mage.cards.s.SunscorchedDesert.class)); - cards.add(new SetCardInfo("Supernatural Stamina", 111, Rarity.COMMON, mage.cards.s.SupernaturalStamina.class)); - cards.add(new SetCardInfo("Supply Caravan", 30, Rarity.COMMON, mage.cards.s.SupplyCaravan.class)); - cards.add(new SetCardInfo("Swamp", 252, Rarity.LAND, mage.cards.basiclands.Swamp.class, new CardGraphicInfo(null, true))); - cards.add(new SetCardInfo("Swamp", 261, Rarity.LAND, mage.cards.basiclands.Swamp.class, new CardGraphicInfo(null, true))); - cards.add(new SetCardInfo("Swamp", 262, Rarity.LAND, mage.cards.basiclands.Swamp.class, new CardGraphicInfo(null, true))); - cards.add(new SetCardInfo("Swamp", 263, Rarity.LAND, mage.cards.basiclands.Swamp.class, new CardGraphicInfo(null, true))); - cards.add(new SetCardInfo("Sweltering Suns", 149, Rarity.RARE, mage.cards.s.SwelteringSuns.class)); - cards.add(new SetCardInfo("Synchronized Strike", 190, Rarity.UNCOMMON, mage.cards.s.SynchronizedStrike.class)); - cards.add(new SetCardInfo("Tah-Crop Elite", 31, Rarity.COMMON, mage.cards.t.TahCropElite.class)); - cards.add(new SetCardInfo("Tah-Crop Skirmisher", 72, Rarity.COMMON, mage.cards.t.TahCropSkirmisher.class)); - cards.add(new SetCardInfo("Tattered Mummy", 278, Rarity.COMMON, mage.cards.t.TatteredMummy.class)); - cards.add(new SetCardInfo("Temmet, Vizier of Naktamun", 207, Rarity.RARE, mage.cards.t.TemmetVizierOfNaktamun.class)); - cards.add(new SetCardInfo("Those Who Serve", 32, Rarity.COMMON, mage.cards.t.ThoseWhoServe.class)); - cards.add(new SetCardInfo("Thresher Lizard", 150, Rarity.COMMON, mage.cards.t.ThresherLizard.class)); - cards.add(new SetCardInfo("Throne of the God-Pharaoh", 237, Rarity.RARE, mage.cards.t.ThroneOfTheGodPharaoh.class)); - cards.add(new SetCardInfo("Time to Reflect", 33, Rarity.UNCOMMON, mage.cards.t.TimeToReflect.class)); - cards.add(new SetCardInfo("Tormenting Voice", 151, Rarity.COMMON, mage.cards.t.TormentingVoice.class)); - cards.add(new SetCardInfo("Trespasser's Curse", 112, Rarity.COMMON, mage.cards.t.TrespassersCurse.class)); - cards.add(new SetCardInfo("Trial of Ambition", 113, Rarity.UNCOMMON, mage.cards.t.TrialOfAmbition.class)); - cards.add(new SetCardInfo("Trial of Knowledge", 73, Rarity.UNCOMMON, mage.cards.t.TrialOfKnowledge.class)); - cards.add(new SetCardInfo("Trial of Solidarity", 34, Rarity.UNCOMMON, mage.cards.t.TrialOfSolidarity.class)); - cards.add(new SetCardInfo("Trial of Strength", 191, Rarity.UNCOMMON, mage.cards.t.TrialOfStrength.class)); - cards.add(new SetCardInfo("Trial of Zeal", 152, Rarity.UNCOMMON, mage.cards.t.TrialOfZeal.class)); - cards.add(new SetCardInfo("Trueheart Duelist", 35, Rarity.UNCOMMON, mage.cards.t.TrueheartDuelist.class)); - cards.add(new SetCardInfo("Trueheart Twins", 153, Rarity.UNCOMMON, mage.cards.t.TrueheartTwins.class)); - cards.add(new SetCardInfo("Unburden", 114, Rarity.COMMON, mage.cards.u.Unburden.class)); - cards.add(new SetCardInfo("Unwavering Initiate", 36, Rarity.COMMON, mage.cards.u.UnwaveringInitiate.class)); - cards.add(new SetCardInfo("Violent Impact", 154, Rarity.COMMON, mage.cards.v.ViolentImpact.class)); - cards.add(new SetCardInfo("Vizier of Deferment", 37, Rarity.UNCOMMON, mage.cards.v.VizierOfDeferment.class)); - cards.add(new SetCardInfo("Vizier of Many Faces", 74, Rarity.RARE, mage.cards.v.VizierOfManyFaces.class)); - cards.add(new SetCardInfo("Vizier of Remedies", 38, Rarity.UNCOMMON, mage.cards.v.VizierOfRemedies.class)); - cards.add(new SetCardInfo("Vizier of Tumbling Sands", 75, Rarity.UNCOMMON, mage.cards.v.VizierOfTumblingSands.class)); - cards.add(new SetCardInfo("Vizier of the Menagerie", 192, Rarity.MYTHIC, mage.cards.v.VizierOfTheMenagerie.class)); - cards.add(new SetCardInfo("Wander in Death", 115, Rarity.COMMON, mage.cards.w.WanderInDeath.class)); - cards.add(new SetCardInfo("Warfire Javelineer", 155, Rarity.UNCOMMON, mage.cards.w.WarfireJavelineer.class)); - cards.add(new SetCardInfo("Wasteland Scorpion", 116, Rarity.COMMON, mage.cards.w.WastelandScorpion.class)); - cards.add(new SetCardInfo("Watchers of the Dead", 238, Rarity.UNCOMMON, mage.cards.w.WatchersOfTheDead.class)); - cards.add(new SetCardInfo("Watchful Naga", 193, Rarity.UNCOMMON, mage.cards.w.WatchfulNaga.class)); - cards.add(new SetCardInfo("Wayward Servant", 208, Rarity.UNCOMMON, mage.cards.w.WaywardServant.class)); - cards.add(new SetCardInfo("Weaver of Currents", 209, Rarity.UNCOMMON, mage.cards.w.WeaverOfCurrents.class)); - cards.add(new SetCardInfo("Winds of Rebuke", 76, Rarity.COMMON, mage.cards.w.WindsOfRebuke.class)); - cards.add(new SetCardInfo("Winged Shepherd", 39, Rarity.COMMON, mage.cards.w.WingedShepherd.class)); - cards.add(new SetCardInfo("Zenith Seeker", 77, Rarity.UNCOMMON, mage.cards.z.ZenithSeeker.class)); - } - - @Override - public List getSpecialLand() { - if (savedSpecialLand.isEmpty()) { - CardCriteria criteria = new CardCriteria(); - criteria.setCodes("MPS-AKH"); - criteria.minCardNumber(1); - criteria.maxCardNumber(30); - savedSpecialLand.addAll(CardRepository.instance.findCards(criteria)); - } - - return new ArrayList<>(savedSpecialLand); - } -} +/* + * Copyright 2011 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets; + +import java.util.ArrayList; +import java.util.List; +import mage.cards.CardGraphicInfo; +import mage.cards.ExpansionSet; +import mage.cards.repository.CardCriteria; +import mage.cards.repository.CardInfo; +import mage.cards.repository.CardRepository; +import mage.constants.Rarity; +import mage.constants.SetType; + +/** + * + * @author fireshoes + */ +public class Amonkhet extends ExpansionSet { + + private static final Amonkhet instance = new Amonkhet(); + + public static Amonkhet getInstance() { + return instance; + } + + protected final List savedSpecialLand = new ArrayList<>(); + + private Amonkhet() { + super("Amonkhet", "AKH", ExpansionSet.buildDate(2017, 4, 28), SetType.EXPANSION); + this.blockName = "Amonkhet"; + this.hasBoosters = true; + this.hasBasicLands = true; + this.numBoosterLands = 1; + this.numBoosterCommon = 10; + this.numBoosterUncommon = 3; + this.numBoosterRare = 1; + this.ratioBoosterMythic = 8; + this.maxCardNumberInBooster = 269; + this.ratioBoosterSpecialLand = 144; + + cards.add(new SetCardInfo("Ahn-Crop Champion", 194, Rarity.UNCOMMON, mage.cards.a.AhnCropChampion.class)); + cards.add(new SetCardInfo("Ahn-Crop Crasher", 117, Rarity.UNCOMMON, mage.cards.a.AhnCropCrasher.class)); + cards.add(new SetCardInfo("Ancient Crab", 40, Rarity.COMMON, mage.cards.a.AncientCrab.class)); + cards.add(new SetCardInfo("Angel of Sanctions", 1, Rarity.MYTHIC, mage.cards.a.AngelOfSanctions.class)); + cards.add(new SetCardInfo("Angler Drake", 41, Rarity.UNCOMMON, mage.cards.a.AnglerDrake.class)); + cards.add(new SetCardInfo("Anointed Procession", 2, Rarity.RARE, mage.cards.a.AnointedProcession.class)); + cards.add(new SetCardInfo("Anointer Priest", 3, Rarity.COMMON, mage.cards.a.AnointerPriest.class)); + cards.add(new SetCardInfo("Approach of the Second Sun", 4, Rarity.RARE, mage.cards.a.ApproachOfTheSecondSun.class)); + cards.add(new SetCardInfo("Archfiend of Ifnir", 78, Rarity.RARE, mage.cards.a.ArchfiendOfIfnir.class)); + cards.add(new SetCardInfo("As Foretold", 42, Rarity.MYTHIC, mage.cards.a.AsForetold.class)); + cards.add(new SetCardInfo("Aven Initiate", 43, Rarity.COMMON, mage.cards.a.AvenInitiate.class)); + cards.add(new SetCardInfo("Aven Mindcensor", 5, Rarity.RARE, mage.cards.a.AvenMindcensor.class)); + cards.add(new SetCardInfo("Aven Wind Guide", 195, Rarity.UNCOMMON, mage.cards.a.AvenWindGuide.class)); + cards.add(new SetCardInfo("Baleful Ammit", 79, Rarity.UNCOMMON, mage.cards.b.BalefulAmmit.class)); + cards.add(new SetCardInfo("Battlefield Scavenger", 118, Rarity.UNCOMMON, mage.cards.b.BattlefieldScavenger.class)); + cards.add(new SetCardInfo("Benefaction of Rhonas", 156, Rarity.COMMON, mage.cards.b.BenefactionOfRhonas.class)); + cards.add(new SetCardInfo("Binding Mummy", 6, Rarity.COMMON, mage.cards.b.BindingMummy.class)); + cards.add(new SetCardInfo("Bitterblade Warrior", 157, Rarity.COMMON, mage.cards.b.BitterbladeWarrior.class)); + cards.add(new SetCardInfo("Blazing Volley", 119, Rarity.COMMON, mage.cards.b.BlazingVolley.class)); + cards.add(new SetCardInfo("Blighted Bat", 80, Rarity.COMMON, mage.cards.b.BlightedBat.class)); + cards.add(new SetCardInfo("Bloodlust Inciter", 120, Rarity.COMMON, mage.cards.b.BloodlustInciter.class)); + cards.add(new SetCardInfo("Bloodrage Brawler", 121, Rarity.UNCOMMON, mage.cards.b.BloodrageBrawler.class)); + cards.add(new SetCardInfo("Bone Picker", 81, Rarity.UNCOMMON, mage.cards.b.BonePicker.class)); + cards.add(new SetCardInfo("Bounty of the Luxa", 196, Rarity.RARE, mage.cards.b.BountyOfTheLuxa.class)); + cards.add(new SetCardInfo("Bontu's Monument", 225, Rarity.UNCOMMON, mage.cards.b.BontusMonument.class)); + cards.add(new SetCardInfo("Bontu the Glorified", 82, Rarity.MYTHIC, mage.cards.b.BontuTheGlorified.class)); + cards.add(new SetCardInfo("Brute Strength", 122, Rarity.COMMON, mage.cards.b.BruteStrength.class)); + cards.add(new SetCardInfo("By Force", 123, Rarity.UNCOMMON, mage.cards.b.ByForce.class)); + cards.add(new SetCardInfo("Cancel", 44, Rarity.COMMON, mage.cards.c.Cancel.class)); + cards.add(new SetCardInfo("Canyon Slough", 239, Rarity.RARE, mage.cards.c.CanyonSlough.class)); + cards.add(new SetCardInfo("Cartouche of Ambition", 83, Rarity.COMMON, mage.cards.c.CartoucheOfAmbition.class)); + cards.add(new SetCardInfo("Cartouche of Knowledge", 45, Rarity.COMMON, mage.cards.c.CartoucheOfKnowledge.class)); + cards.add(new SetCardInfo("Cartouche of Solidarity", 7, Rarity.COMMON, mage.cards.c.CartoucheOfSolidarity.class)); + cards.add(new SetCardInfo("Cartouche of Strength", 158, Rarity.COMMON, mage.cards.c.CartoucheOfStrength.class)); + cards.add(new SetCardInfo("Cartouche of Zeal", 124, Rarity.COMMON, mage.cards.c.CartoucheOfZeal.class)); + cards.add(new SetCardInfo("Cascading Cataracts", 240, Rarity.RARE, mage.cards.c.CascadingCataracts.class)); + cards.add(new SetCardInfo("Cast Out", 8, Rarity.UNCOMMON, mage.cards.c.CastOut.class)); + cards.add(new SetCardInfo("Censor", 46, Rarity.UNCOMMON, mage.cards.c.Censor.class)); + cards.add(new SetCardInfo("Champion of Rhonas", 159, Rarity.RARE, mage.cards.c.ChampionOfRhonas.class)); + cards.add(new SetCardInfo("Channeler Initiate", 160, Rarity.RARE, mage.cards.c.ChannelerInitiate.class)); + cards.add(new SetCardInfo("Cinder Barrens", 280, Rarity.COMMON, mage.cards.c.CinderBarrens.class)); + cards.add(new SetCardInfo("Colossapede", 161, Rarity.COMMON, mage.cards.c.Colossapede.class)); + cards.add(new SetCardInfo("Combat Celebrant", 125, Rarity.MYTHIC, mage.cards.c.CombatCelebrant.class)); + cards.add(new SetCardInfo("Commit // Memory", 211, Rarity.RARE, mage.cards.c.CommitMemory.class)); + cards.add(new SetCardInfo("Companion of the Trials", 271, Rarity.UNCOMMON, mage.cards.c.CompanionOfTheTrials.class)); + cards.add(new SetCardInfo("Compelling Argument", 47, Rarity.COMMON, mage.cards.c.CompellingArgument.class)); + cards.add(new SetCardInfo("Compulsory Rest", 9, Rarity.COMMON, mage.cards.c.CompulsoryRest.class)); + cards.add(new SetCardInfo("Consuming Fervor", 126, Rarity.UNCOMMON, mage.cards.c.ConsumingFervor.class)); + cards.add(new SetCardInfo("Cradle of the Accursed", 241, Rarity.COMMON, mage.cards.c.CradleOfTheAccursed.class)); + cards.add(new SetCardInfo("Crocodile of the Crossing", 162, Rarity.UNCOMMON, mage.cards.c.CrocodileOfTheCrossing.class)); + cards.add(new SetCardInfo("Cryptic Serpent", 48, Rarity.UNCOMMON, mage.cards.c.CrypticSerpent.class)); + cards.add(new SetCardInfo("Cruel Reality", 84, Rarity.MYTHIC, mage.cards.c.CruelReality.class)); + cards.add(new SetCardInfo("Curator of Mysteries", 49, Rarity.RARE, mage.cards.c.CuratorOfMysteries.class)); + cards.add(new SetCardInfo("Cursed Minotaur", 85, Rarity.COMMON, mage.cards.c.CursedMinotaur.class)); + cards.add(new SetCardInfo("Cut // Ribbons", 223, Rarity.RARE, mage.cards.c.CutRibbons.class)); + cards.add(new SetCardInfo("Decimator Beetle", 197, Rarity.UNCOMMON, mage.cards.d.DecimatorBeetle.class)); + cards.add(new SetCardInfo("Decision Paralysis", 50, Rarity.COMMON, mage.cards.d.DecisionParalysis.class)); + cards.add(new SetCardInfo("Deem Worthy", 127, Rarity.UNCOMMON, mage.cards.d.DeemWorthy.class)); + cards.add(new SetCardInfo("Defiant Greatmaw", 163, Rarity.UNCOMMON, mage.cards.d.DefiantGreatmaw.class)); + cards.add(new SetCardInfo("Desert Cerodon", 128, Rarity.COMMON, mage.cards.d.DesertCerodon.class)); + cards.add(new SetCardInfo("Desiccated Naga", 276, Rarity.UNCOMMON, mage.cards.d.DesiccatedNaga.class)); + cards.add(new SetCardInfo("Destined // Lead", 217, Rarity.UNCOMMON, mage.cards.d.DestinedLead.class)); + cards.add(new SetCardInfo("Devoted Crop-Mate", 10, Rarity.UNCOMMON, mage.cards.d.DevotedCropMate.class)); + cards.add(new SetCardInfo("Dispossess", 86, Rarity.RARE, mage.cards.d.Dispossess.class)); + cards.add(new SetCardInfo("Dissenter's Deliverance", 164, Rarity.COMMON, mage.cards.d.DissentersDeliverance.class)); + cards.add(new SetCardInfo("Djeru's Resolve", 11, Rarity.COMMON, mage.cards.d.DjerusResolve.class)); + cards.add(new SetCardInfo("Doomed Dissenter", 87, Rarity.COMMON, mage.cards.d.DoomedDissenter.class)); + cards.add(new SetCardInfo("Drake Haven", 51, Rarity.RARE, mage.cards.d.DrakeHaven.class)); + cards.add(new SetCardInfo("Dread Wanderer", 88, Rarity.RARE, mage.cards.d.DreadWanderer.class)); + cards.add(new SetCardInfo("Dune Beetle", 89, Rarity.COMMON, mage.cards.d.DuneBeetle.class)); + cards.add(new SetCardInfo("Dusk // Dawn", 210, Rarity.RARE, mage.cards.d.DuskDawn.class)); + cards.add(new SetCardInfo("Edifice of Authority", 226, Rarity.UNCOMMON, mage.cards.e.EdificeOfAuthority.class)); + cards.add(new SetCardInfo("Electrify", 129, Rarity.COMMON, mage.cards.e.Electrify.class)); + cards.add(new SetCardInfo("Embalmer's Tools", 227, Rarity.UNCOMMON, mage.cards.e.EmbalmersTools.class)); + cards.add(new SetCardInfo("Emberhorn Minotaur", 130, Rarity.COMMON, mage.cards.e.EmberhornMinotaur.class)); + cards.add(new SetCardInfo("Enigma Drake", 198, Rarity.UNCOMMON, mage.cards.e.EnigmaDrake.class)); + cards.add(new SetCardInfo("Essence Scatter", 52, Rarity.COMMON, mage.cards.e.EssenceScatter.class)); + cards.add(new SetCardInfo("Evolving Wilds", 242, Rarity.COMMON, mage.cards.e.EvolvingWilds.class)); + cards.add(new SetCardInfo("Exemplar of Strength", 165, Rarity.UNCOMMON, mage.cards.e.ExemplarOfStrength.class)); + cards.add(new SetCardInfo("Failure // Comply", 221, Rarity.RARE, mage.cards.f.FailureComply.class)); + cards.add(new SetCardInfo("Faith of the Devoted", 90, Rarity.UNCOMMON, mage.cards.f.FaithOfTheDevoted.class)); + cards.add(new SetCardInfo("Fan Bearer", 12, Rarity.COMMON, mage.cards.f.FanBearer.class)); + cards.add(new SetCardInfo("Festering Mummy", 91, Rarity.COMMON, mage.cards.f.FesteringMummy.class)); + cards.add(new SetCardInfo("Fetid Pools", 243, Rarity.RARE, mage.cards.f.FetidPools.class)); + cards.add(new SetCardInfo("Final Reward", 92, Rarity.COMMON, mage.cards.f.FinalReward.class)); + cards.add(new SetCardInfo("Flameblade Adept", 131, Rarity.UNCOMMON, mage.cards.f.FlamebladeAdept.class)); + cards.add(new SetCardInfo("Fling", 132, Rarity.COMMON, mage.cards.f.Fling.class)); + cards.add(new SetCardInfo("Floodwaters", 53, Rarity.COMMON, mage.cards.f.Floodwaters.class)); + cards.add(new SetCardInfo("Forest", 254, Rarity.LAND, mage.cards.basiclands.Forest.class, new CardGraphicInfo(null, true))); + cards.add(new SetCardInfo("Forest", 267, Rarity.LAND, mage.cards.basiclands.Forest.class, new CardGraphicInfo(null, true))); + cards.add(new SetCardInfo("Forest", 268, Rarity.LAND, mage.cards.basiclands.Forest.class, new CardGraphicInfo(null, true))); + cards.add(new SetCardInfo("Forest", 269, Rarity.LAND, mage.cards.basiclands.Forest.class, new CardGraphicInfo(null, true))); + cards.add(new SetCardInfo("Forsake the Worldly", 13, Rarity.COMMON, mage.cards.f.ForsakeTheWorldly.class)); + cards.add(new SetCardInfo("Forsaken Sanctuary", 281, Rarity.COMMON, mage.cards.f.ForsakenSanctuary.class)); + cards.add(new SetCardInfo("Foul Orchard", 279, Rarity.COMMON, mage.cards.f.FoulOrchard.class)); + cards.add(new SetCardInfo("Galestrike", 54, Rarity.UNCOMMON, mage.cards.g.Galestrike.class)); + cards.add(new SetCardInfo("Gate to the Afterlife", 228, Rarity.UNCOMMON, mage.cards.g.GateToTheAfterlife.class)); + cards.add(new SetCardInfo("Giant Spider", 166, Rarity.COMMON, mage.cards.g.GiantSpider.class)); + cards.add(new SetCardInfo("Gideon of the Trials", 14, Rarity.MYTHIC, mage.cards.g.GideonOfTheTrials.class)); + cards.add(new SetCardInfo("Gideon's Intervention", 15, Rarity.RARE, mage.cards.g.GideonsIntervention.class)); + cards.add(new SetCardInfo("Gideon's Resolve", 272, Rarity.RARE, mage.cards.g.GideonsResolve.class)); + cards.add(new SetCardInfo("Gideon, Martial Paragon", 270, Rarity.MYTHIC, mage.cards.g.GideonMartialParagon.class)); + cards.add(new SetCardInfo("Gift of Paradise", 167, Rarity.COMMON, mage.cards.g.GiftOfParadise.class)); + cards.add(new SetCardInfo("Glorious End", 133, Rarity.MYTHIC, mage.cards.g.GloriousEnd.class)); + cards.add(new SetCardInfo("Glory-Bound Initiate", 16, Rarity.RARE, mage.cards.g.GloryBoundInitiate.class)); + cards.add(new SetCardInfo("Glorybringer", 134, Rarity.RARE, mage.cards.g.Glorybringer.class)); + cards.add(new SetCardInfo("Glyph Keeper", 55, Rarity.RARE, mage.cards.g.GlyphKeeper.class)); + cards.add(new SetCardInfo("Graceful Cat", 273, Rarity.COMMON, mage.cards.g.GracefulCat.class)); + cards.add(new SetCardInfo("Grasping Dunes", 244, Rarity.UNCOMMON, mage.cards.g.GraspingDunes.class)); + cards.add(new SetCardInfo("Gravedigger", 93, Rarity.UNCOMMON, mage.cards.g.Gravedigger.class)); + cards.add(new SetCardInfo("Greater Sandwurm", 168, Rarity.COMMON, mage.cards.g.GreaterSandwurm.class)); + cards.add(new SetCardInfo("Grim Strider", 94, Rarity.UNCOMMON, mage.cards.g.GrimStrider.class)); + cards.add(new SetCardInfo("Gust Walker", 17, Rarity.COMMON, mage.cards.g.GustWalker.class)); + cards.add(new SetCardInfo("Hapatra's Mark", 169, Rarity.UNCOMMON, mage.cards.h.HapatrasMark.class)); + cards.add(new SetCardInfo("Hapatra, Vizier of Poisons", 199, Rarity.RARE, mage.cards.h.HapatraVizierOfPoisons.class)); + cards.add(new SetCardInfo("Harsh Mentor", 135, Rarity.RARE, mage.cards.h.HarshMentor.class)); + cards.add(new SetCardInfo("Harvest Season", 170, Rarity.RARE, mage.cards.h.HarvestSeason.class)); + cards.add(new SetCardInfo("Haze of Pollen", 171, Rarity.COMMON, mage.cards.h.HazeOfPollen.class)); + cards.add(new SetCardInfo("Hazoret the Fervent", 136, Rarity.MYTHIC, mage.cards.h.HazoretTheFervent.class)); + cards.add(new SetCardInfo("Hazoret's Favor", 137, Rarity.RARE, mage.cards.h.HazoretsFavor.class)); + cards.add(new SetCardInfo("Hazoret's Monument", 229, Rarity.UNCOMMON, mage.cards.h.HazoretsMonument.class)); + cards.add(new SetCardInfo("Heart-Piercer Manticore", 138, Rarity.RARE, mage.cards.h.HeartPiercerManticore.class)); + cards.add(new SetCardInfo("Heaven // Earth", 224, Rarity.RARE, mage.cards.h.HeavenEarth.class)); + cards.add(new SetCardInfo("Hekma Sentinels", 56, Rarity.COMMON, mage.cards.h.HekmaSentinels.class)); + cards.add(new SetCardInfo("Hieroglyphic Illumination", 57, Rarity.COMMON, mage.cards.h.HieroglyphicIllumination.class)); + cards.add(new SetCardInfo("Highland Lake", 282, Rarity.COMMON, mage.cards.h.HighlandLake.class)); + cards.add(new SetCardInfo("Honed Khopesh", 230, Rarity.COMMON, mage.cards.h.HonedKhopesh.class)); + cards.add(new SetCardInfo("Honored Crop-Captain", 200, Rarity.UNCOMMON, mage.cards.h.HonoredCropCaptain.class)); + cards.add(new SetCardInfo("Honored Hydra", 172, Rarity.RARE, mage.cards.h.HonoredHydra.class)); + cards.add(new SetCardInfo("Hooded Brawler", 173, Rarity.COMMON, mage.cards.h.HoodedBrawler.class)); + cards.add(new SetCardInfo("Horror of the Broken Lands", 95, Rarity.COMMON, mage.cards.h.HorrorOfTheBrokenLands.class)); + cards.add(new SetCardInfo("Hyena Pack", 139, Rarity.COMMON, mage.cards.h.HyenaPack.class)); + cards.add(new SetCardInfo("Illusory Wrappings", 58, Rarity.COMMON, mage.cards.i.IllusoryWrappings.class)); + cards.add(new SetCardInfo("Impeccable Timing", 18, Rarity.COMMON, mage.cards.i.ImpeccableTiming.class)); + cards.add(new SetCardInfo("In Oketra's Name", 19, Rarity.COMMON, mage.cards.i.InOketrasName.class)); + cards.add(new SetCardInfo("Initiate's Companion", 174, Rarity.COMMON, mage.cards.i.InitiatesCompanion.class)); + cards.add(new SetCardInfo("Insult // Injury", 213, Rarity.RARE, mage.cards.i.InsultInjury.class)); + cards.add(new SetCardInfo("Irrigated Farmland", 245, Rarity.RARE, mage.cards.i.IrrigatedFarmland.class)); + cards.add(new SetCardInfo("Island", 251, Rarity.LAND, mage.cards.basiclands.Island.class, new CardGraphicInfo(null, true))); + cards.add(new SetCardInfo("Island", 258, Rarity.LAND, mage.cards.basiclands.Island.class, new CardGraphicInfo(null, true))); + cards.add(new SetCardInfo("Island", 259, Rarity.LAND, mage.cards.basiclands.Island.class, new CardGraphicInfo(null, true))); + cards.add(new SetCardInfo("Island", 260, Rarity.LAND, mage.cards.basiclands.Island.class, new CardGraphicInfo(null, true))); + cards.add(new SetCardInfo("Kefnet the Mindful", 59, Rarity.MYTHIC, mage.cards.k.KefnetTheMindful.class)); + cards.add(new SetCardInfo("Kefnet's Monument", 231, Rarity.UNCOMMON, mage.cards.k.KefnetsMonument.class)); + cards.add(new SetCardInfo("Khenra Charioteer", 201, Rarity.UNCOMMON, mage.cards.k.KhenraCharioteer.class)); + cards.add(new SetCardInfo("Labyrinth Guardian", 60, Rarity.UNCOMMON, mage.cards.l.LabyrinthGuardian.class)); + cards.add(new SetCardInfo("Lay Bare the Heart", 96, Rarity.UNCOMMON, mage.cards.l.LayBareTheHeart.class)); + cards.add(new SetCardInfo("Lay Claim", 61, Rarity.UNCOMMON, mage.cards.l.LayClaim.class)); + cards.add(new SetCardInfo("Liliana's Influence", 277, Rarity.RARE, mage.cards.l.LilianasInfluence.class)); + cards.add(new SetCardInfo("Liliana's Mastery", 98, Rarity.RARE, mage.cards.l.LilianasMastery.class)); + cards.add(new SetCardInfo("Liliana, Death Wielder", 275, Rarity.MYTHIC, mage.cards.l.LilianaDeathWielder.class)); + cards.add(new SetCardInfo("Liliana, Death's Majesty", 97, Rarity.MYTHIC, mage.cards.l.LilianaDeathsMajesty.class)); + cards.add(new SetCardInfo("Limits of Solidarity", 140, Rarity.UNCOMMON, mage.cards.l.LimitsOfSolidarity.class)); + cards.add(new SetCardInfo("Lord of the Accursed", 99, Rarity.UNCOMMON, mage.cards.l.LordOfTheAccursed.class)); + cards.add(new SetCardInfo("Luxa River Shrine", 232, Rarity.COMMON, mage.cards.l.LuxaRiverShrine.class)); + cards.add(new SetCardInfo("Magma Spray", 141, Rarity.COMMON, mage.cards.m.MagmaSpray.class)); + cards.add(new SetCardInfo("Manglehorn", 175, Rarity.UNCOMMON, mage.cards.m.Manglehorn.class)); + cards.add(new SetCardInfo("Manticore of the Gauntlet", 142, Rarity.COMMON, mage.cards.m.ManticoreOfTheGauntlet.class)); + cards.add(new SetCardInfo("Meandering River", 283, Rarity.COMMON, mage.cards.m.MeanderingRiver.class)); + cards.add(new SetCardInfo("Merciless Javelineer", 202, Rarity.UNCOMMON, mage.cards.m.MercilessJavelineer.class)); + cards.add(new SetCardInfo("Miasmic Mummy", 100, Rarity.COMMON, mage.cards.m.MiasmicMummy.class)); + cards.add(new SetCardInfo("Mighty Leap", 20, Rarity.COMMON, mage.cards.m.MightyLeap.class)); + cards.add(new SetCardInfo("Minotaur Sureshot", 143, Rarity.COMMON, mage.cards.m.MinotaurSureshot.class)); + cards.add(new SetCardInfo("Mountain", 253, Rarity.LAND, mage.cards.basiclands.Mountain.class, new CardGraphicInfo(null, true))); + cards.add(new SetCardInfo("Mountain", 264, Rarity.LAND, mage.cards.basiclands.Mountain.class, new CardGraphicInfo(null, true))); + cards.add(new SetCardInfo("Mountain", 265, Rarity.LAND, mage.cards.basiclands.Mountain.class, new CardGraphicInfo(null, true))); + cards.add(new SetCardInfo("Mountain", 266, Rarity.LAND, mage.cards.basiclands.Mountain.class, new CardGraphicInfo(null, true))); + cards.add(new SetCardInfo("Mouth // Feed", 214, Rarity.RARE, mage.cards.m.MouthFeed.class)); + cards.add(new SetCardInfo("Naga Oracle", 62, Rarity.COMMON, mage.cards.n.NagaOracle.class)); + cards.add(new SetCardInfo("Naga Vitalist", 176, Rarity.COMMON, mage.cards.n.NagaVitalist.class)); + cards.add(new SetCardInfo("Nef-Crop Entangler", 144, Rarity.COMMON, mage.cards.n.NefCropEntangler.class)); + cards.add(new SetCardInfo("Neheb, the Worthy", 203, Rarity.RARE, mage.cards.n.NehebTheWorthy.class)); + cards.add(new SetCardInfo("Nest of Scarabs", 101, Rarity.UNCOMMON, mage.cards.n.NestOfScarabs.class)); + cards.add(new SetCardInfo("Never // Return", 212, Rarity.RARE, mage.cards.n.NeverReturn.class)); + cards.add(new SetCardInfo("New Perspectives", 63, Rarity.RARE, mage.cards.n.NewPerspectives.class)); + cards.add(new SetCardInfo("Nimble-Blade Khenra", 145, Rarity.COMMON, mage.cards.n.NimbleBladeKhenra.class)); + cards.add(new SetCardInfo("Nissa, Steward of Elements", 204, Rarity.MYTHIC, mage.cards.n.NissaStewardOfElements.class)); + cards.add(new SetCardInfo("Oashra Cultivator", 177, Rarity.COMMON, mage.cards.o.OashraCultivator.class)); + cards.add(new SetCardInfo("Oketra the True", 21, Rarity.MYTHIC, mage.cards.o.OketraTheTrue.class)); + cards.add(new SetCardInfo("Oketra's Attendant", 22, Rarity.UNCOMMON, mage.cards.o.OketrasAttendant.class)); + cards.add(new SetCardInfo("Oketra's Monument", 233, Rarity.UNCOMMON, mage.cards.o.OketrasMonument.class)); + cards.add(new SetCardInfo("Onward // Victory", 218, Rarity.UNCOMMON, mage.cards.o.OnwardVictory.class)); + cards.add(new SetCardInfo("Open into Wonder", 64, Rarity.UNCOMMON, mage.cards.o.OpenIntoWonder.class)); + cards.add(new SetCardInfo("Oracle's Vault", 234, Rarity.RARE, mage.cards.o.OraclesVault.class)); + cards.add(new SetCardInfo("Ornery Kudu", 178, Rarity.COMMON, mage.cards.o.OrneryKudu.class)); + cards.add(new SetCardInfo("Painful Lesson", 102, Rarity.COMMON, mage.cards.p.PainfulLesson.class)); + cards.add(new SetCardInfo("Painted Bluffs", 246, Rarity.COMMON, mage.cards.p.PaintedBluffs.class)); + cards.add(new SetCardInfo("Pathmaker Initiate", 146, Rarity.COMMON, mage.cards.p.PathmakerInitiate.class)); + cards.add(new SetCardInfo("Pitiless Vizier", 103, Rarity.COMMON, mage.cards.p.PitilessVizier.class)); + cards.add(new SetCardInfo("Plague Belcher", 104, Rarity.RARE, mage.cards.p.PlagueBelcher.class)); + cards.add(new SetCardInfo("Pyramid of the Pantheon", 235, Rarity.RARE, mage.cards.p.PyramidOfThePantheon.class)); + cards.add(new SetCardInfo("Plains", 250, Rarity.LAND, mage.cards.basiclands.Plains.class, new CardGraphicInfo(null, true))); + cards.add(new SetCardInfo("Plains", 255, Rarity.LAND, mage.cards.basiclands.Plains.class, new CardGraphicInfo(null, true))); + cards.add(new SetCardInfo("Plains", 256, Rarity.LAND, mage.cards.basiclands.Plains.class, new CardGraphicInfo(null, true))); + cards.add(new SetCardInfo("Plains", 257, Rarity.LAND, mage.cards.basiclands.Plains.class, new CardGraphicInfo(null, true))); + cards.add(new SetCardInfo("Pouncing Cheetah", 179, Rarity.COMMON, mage.cards.p.PouncingCheetah.class)); + cards.add(new SetCardInfo("Prepare // Fight", 220, Rarity.RARE, mage.cards.p.PrepareFight.class)); + cards.add(new SetCardInfo("Protection of the Hekma", 23, Rarity.UNCOMMON, mage.cards.p.ProtectionOfTheHekma.class)); + cards.add(new SetCardInfo("Prowling Serpopard", 180, Rarity.RARE, mage.cards.p.ProwlingSerpopard.class)); + cards.add(new SetCardInfo("Pull from Tomorrow", 65, Rarity.RARE, mage.cards.p.PullFromTomorrow.class)); + cards.add(new SetCardInfo("Pursue Glory", 147, Rarity.COMMON, mage.cards.p.PursueGlory.class)); + cards.add(new SetCardInfo("Quarry Hauler", 181, Rarity.COMMON, mage.cards.q.QuarryHauler.class)); + cards.add(new SetCardInfo("Rags // Riches", 222, Rarity.RARE, mage.cards.r.RagsRiches.class)); + cards.add(new SetCardInfo("Reduce // Rubble", 216, Rarity.UNCOMMON, mage.cards.r.ReduceRubble.class)); + cards.add(new SetCardInfo("Regal Caracal", 24, Rarity.RARE, mage.cards.r.RegalCaracal.class)); + cards.add(new SetCardInfo("Renewed Faith", 25, Rarity.UNCOMMON, mage.cards.r.RenewedFaith.class)); + cards.add(new SetCardInfo("Rhet-Crop Spearmaster", 26, Rarity.COMMON, mage.cards.r.RhetCropSpearmaster.class)); + cards.add(new SetCardInfo("Rhonas's Monument", 236, Rarity.UNCOMMON, mage.cards.r.RhonassMonument.class)); + cards.add(new SetCardInfo("Rhonas the Indomitable", 182, Rarity.MYTHIC, mage.cards.r.RhonasTheIndomitable.class)); + cards.add(new SetCardInfo("River Serpent", 66, Rarity.COMMON, mage.cards.r.RiverSerpent.class)); + cards.add(new SetCardInfo("Ruthless Sniper", 105, Rarity.UNCOMMON, mage.cards.r.RuthlessSniper.class)); + cards.add(new SetCardInfo("Sacred Cat", 27, Rarity.COMMON, mage.cards.s.SacredCat.class)); + cards.add(new SetCardInfo("Sacred Excavation", 67, Rarity.UNCOMMON, mage.cards.s.SacredExcavation.class)); + cards.add(new SetCardInfo("Samut, Voice of Dissent", 205, Rarity.MYTHIC, mage.cards.s.SamutVoiceOfDissent.class)); + cards.add(new SetCardInfo("Sandwurm Convergence", 183, Rarity.RARE, mage.cards.s.SandwurmConvergence.class)); + cards.add(new SetCardInfo("Scaled Behemoth", 184, Rarity.UNCOMMON, mage.cards.s.ScaledBehemoth.class)); + cards.add(new SetCardInfo("Scarab Feast", 106, Rarity.COMMON, mage.cards.s.ScarabFeast.class)); + cards.add(new SetCardInfo("Scattered Groves", 247, Rarity.RARE, mage.cards.s.ScatteredGroves.class)); + cards.add(new SetCardInfo("Scribe of the Mindful", 68, Rarity.COMMON, mage.cards.s.ScribeOfTheMindful.class)); + cards.add(new SetCardInfo("Seeker of Insight", 69, Rarity.COMMON, mage.cards.s.SeekerOfInsight.class)); + cards.add(new SetCardInfo("Seraph of the Suns", 28, Rarity.UNCOMMON, mage.cards.s.SeraphOfTheSuns.class)); + cards.add(new SetCardInfo("Shadow of the Grave", 107, Rarity.RARE, mage.cards.s.ShadowOfTheGrave.class)); + cards.add(new SetCardInfo("Shadowstorm Vizier", 206, Rarity.UNCOMMON, mage.cards.s.ShadowstormVizier.class)); + cards.add(new SetCardInfo("Shed Weakness", 185, Rarity.COMMON, mage.cards.s.ShedWeakness.class)); + cards.add(new SetCardInfo("Shefet Monitor", 186, Rarity.UNCOMMON, mage.cards.s.ShefetMonitor.class)); + cards.add(new SetCardInfo("Sheltered Thicket", 248, Rarity.RARE, mage.cards.s.ShelteredThicket.class)); + cards.add(new SetCardInfo("Shimmerscale Drake", 70, Rarity.COMMON, mage.cards.s.ShimmerscaleDrake.class)); + cards.add(new SetCardInfo("Sixth Sense", 187, Rarity.UNCOMMON, mage.cards.s.SixthSense.class)); + cards.add(new SetCardInfo("Slither Blade", 71, Rarity.COMMON, mage.cards.s.SlitherBlade.class)); + cards.add(new SetCardInfo("Soul-Scar Mage", 148, Rarity.RARE, mage.cards.s.SoulScarMage.class)); + cards.add(new SetCardInfo("Soulstinger", 108, Rarity.COMMON, mage.cards.s.Soulstinger.class)); + cards.add(new SetCardInfo("Sparring Mummy", 29, Rarity.COMMON, mage.cards.s.SparringMummy.class)); + cards.add(new SetCardInfo("Spidery Grasp", 188, Rarity.COMMON, mage.cards.s.SpideryGrasp.class)); + cards.add(new SetCardInfo("Splendid Agony", 109, Rarity.COMMON, mage.cards.s.SplendidAgony.class)); + cards.add(new SetCardInfo("Spring // Mind", 219, Rarity.UNCOMMON, mage.cards.s.SpringMind.class)); + cards.add(new SetCardInfo("Start // Finish", 215, Rarity.UNCOMMON, mage.cards.s.StartFinish.class)); + cards.add(new SetCardInfo("Stinging Shot", 189, Rarity.COMMON, mage.cards.s.StingingShot.class)); + cards.add(new SetCardInfo("Stir the Sands", 110, Rarity.UNCOMMON, mage.cards.s.StirTheSands.class)); + cards.add(new SetCardInfo("Stone Quarry", 274, Rarity.COMMON, mage.cards.s.StoneQuarry.class)); + cards.add(new SetCardInfo("Submerged Boneyard", 284, Rarity.COMMON, mage.cards.s.SubmergedBoneyard.class)); + cards.add(new SetCardInfo("Sunscorched Desert", 249, Rarity.COMMON, mage.cards.s.SunscorchedDesert.class)); + cards.add(new SetCardInfo("Supernatural Stamina", 111, Rarity.COMMON, mage.cards.s.SupernaturalStamina.class)); + cards.add(new SetCardInfo("Supply Caravan", 30, Rarity.COMMON, mage.cards.s.SupplyCaravan.class)); + cards.add(new SetCardInfo("Swamp", 252, Rarity.LAND, mage.cards.basiclands.Swamp.class, new CardGraphicInfo(null, true))); + cards.add(new SetCardInfo("Swamp", 261, Rarity.LAND, mage.cards.basiclands.Swamp.class, new CardGraphicInfo(null, true))); + cards.add(new SetCardInfo("Swamp", 262, Rarity.LAND, mage.cards.basiclands.Swamp.class, new CardGraphicInfo(null, true))); + cards.add(new SetCardInfo("Swamp", 263, Rarity.LAND, mage.cards.basiclands.Swamp.class, new CardGraphicInfo(null, true))); + cards.add(new SetCardInfo("Sweltering Suns", 149, Rarity.RARE, mage.cards.s.SwelteringSuns.class)); + cards.add(new SetCardInfo("Synchronized Strike", 190, Rarity.UNCOMMON, mage.cards.s.SynchronizedStrike.class)); + cards.add(new SetCardInfo("Tah-Crop Elite", 31, Rarity.COMMON, mage.cards.t.TahCropElite.class)); + cards.add(new SetCardInfo("Tah-Crop Skirmisher", 72, Rarity.COMMON, mage.cards.t.TahCropSkirmisher.class)); + cards.add(new SetCardInfo("Tattered Mummy", 278, Rarity.COMMON, mage.cards.t.TatteredMummy.class)); + cards.add(new SetCardInfo("Temmet, Vizier of Naktamun", 207, Rarity.RARE, mage.cards.t.TemmetVizierOfNaktamun.class)); + cards.add(new SetCardInfo("Those Who Serve", 32, Rarity.COMMON, mage.cards.t.ThoseWhoServe.class)); + cards.add(new SetCardInfo("Thresher Lizard", 150, Rarity.COMMON, mage.cards.t.ThresherLizard.class)); + cards.add(new SetCardInfo("Throne of the God-Pharaoh", 237, Rarity.RARE, mage.cards.t.ThroneOfTheGodPharaoh.class)); + cards.add(new SetCardInfo("Timber Gorge", 285, Rarity.COMMON, mage.cards.t.TimberGorge.class)); + cards.add(new SetCardInfo("Time to Reflect", 33, Rarity.UNCOMMON, mage.cards.t.TimeToReflect.class)); + cards.add(new SetCardInfo("Tormenting Voice", 151, Rarity.COMMON, mage.cards.t.TormentingVoice.class)); + cards.add(new SetCardInfo("Tranquil Expanse", 286, Rarity.COMMON, mage.cards.t.TranquilExpanse.class)); + cards.add(new SetCardInfo("Trespasser's Curse", 112, Rarity.COMMON, mage.cards.t.TrespassersCurse.class)); + cards.add(new SetCardInfo("Trial of Ambition", 113, Rarity.UNCOMMON, mage.cards.t.TrialOfAmbition.class)); + cards.add(new SetCardInfo("Trial of Knowledge", 73, Rarity.UNCOMMON, mage.cards.t.TrialOfKnowledge.class)); + cards.add(new SetCardInfo("Trial of Solidarity", 34, Rarity.UNCOMMON, mage.cards.t.TrialOfSolidarity.class)); + cards.add(new SetCardInfo("Trial of Strength", 191, Rarity.UNCOMMON, mage.cards.t.TrialOfStrength.class)); + cards.add(new SetCardInfo("Trial of Zeal", 152, Rarity.UNCOMMON, mage.cards.t.TrialOfZeal.class)); + cards.add(new SetCardInfo("Trueheart Duelist", 35, Rarity.UNCOMMON, mage.cards.t.TrueheartDuelist.class)); + cards.add(new SetCardInfo("Trueheart Twins", 153, Rarity.UNCOMMON, mage.cards.t.TrueheartTwins.class)); + cards.add(new SetCardInfo("Unburden", 114, Rarity.COMMON, mage.cards.u.Unburden.class)); + cards.add(new SetCardInfo("Unwavering Initiate", 36, Rarity.COMMON, mage.cards.u.UnwaveringInitiate.class)); + cards.add(new SetCardInfo("Violent Impact", 154, Rarity.COMMON, mage.cards.v.ViolentImpact.class)); + cards.add(new SetCardInfo("Vizier of Deferment", 37, Rarity.UNCOMMON, mage.cards.v.VizierOfDeferment.class)); + cards.add(new SetCardInfo("Vizier of Many Faces", 74, Rarity.RARE, mage.cards.v.VizierOfManyFaces.class)); + cards.add(new SetCardInfo("Vizier of Remedies", 38, Rarity.UNCOMMON, mage.cards.v.VizierOfRemedies.class)); + cards.add(new SetCardInfo("Vizier of Tumbling Sands", 75, Rarity.UNCOMMON, mage.cards.v.VizierOfTumblingSands.class)); + cards.add(new SetCardInfo("Vizier of the Menagerie", 192, Rarity.MYTHIC, mage.cards.v.VizierOfTheMenagerie.class)); + cards.add(new SetCardInfo("Wander in Death", 115, Rarity.COMMON, mage.cards.w.WanderInDeath.class)); + cards.add(new SetCardInfo("Warfire Javelineer", 155, Rarity.UNCOMMON, mage.cards.w.WarfireJavelineer.class)); + cards.add(new SetCardInfo("Wasteland Scorpion", 116, Rarity.COMMON, mage.cards.w.WastelandScorpion.class)); + cards.add(new SetCardInfo("Watchers of the Dead", 238, Rarity.UNCOMMON, mage.cards.w.WatchersOfTheDead.class)); + cards.add(new SetCardInfo("Watchful Naga", 193, Rarity.UNCOMMON, mage.cards.w.WatchfulNaga.class)); + cards.add(new SetCardInfo("Wayward Servant", 208, Rarity.UNCOMMON, mage.cards.w.WaywardServant.class)); + cards.add(new SetCardInfo("Weaver of Currents", 209, Rarity.UNCOMMON, mage.cards.w.WeaverOfCurrents.class)); + cards.add(new SetCardInfo("Winds of Rebuke", 76, Rarity.COMMON, mage.cards.w.WindsOfRebuke.class)); + cards.add(new SetCardInfo("Winged Shepherd", 39, Rarity.COMMON, mage.cards.w.WingedShepherd.class)); + cards.add(new SetCardInfo("Woodland Stream", 287, Rarity.COMMON, mage.cards.w.WoodlandStream.class)); + cards.add(new SetCardInfo("Zenith Seeker", 77, Rarity.UNCOMMON, mage.cards.z.ZenithSeeker.class)); + } + + @Override + public List getSpecialLand() { + if (savedSpecialLand.isEmpty()) { + CardCriteria criteria = new CardCriteria(); + criteria.setCodes("MPS-AKH"); + criteria.minCardNumber(1); + criteria.maxCardNumber(30); + savedSpecialLand.addAll(CardRepository.instance.findCards(criteria)); + } + + return new ArrayList<>(savedSpecialLand); + } +} From 5373997b309ddb4f26dbb4a5136bbb094bd8610c Mon Sep 17 00:00:00 2001 From: spjspj Date: Wed, 19 Apr 2017 10:30:40 +1000 Subject: [PATCH 11/22] Undo double tap of ok button (Leading to duplicate tournaments - Fixes #3154) --- .../src/main/java/mage/client/dialog/NewTournamentDialog.java | 3 --- 1 file changed, 3 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 fec1372e90..7baf4abf42 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java @@ -322,9 +322,6 @@ public class NewTournamentDialog extends MageDialog { btnOk.setText("OK"); btnOk.addActionListener(evt -> btnOkActionPerformed(evt)); - btnOk.setText("OK"); - btnOk.addActionListener(evt -> btnOkActionPerformed(evt)); - btnCancel.setText("Cancel"); btnCancel.addActionListener(evt -> btnCancelActionPerformed(evt)); From b3fb1429989fc1a950a1112e27f8ad7db46198ff Mon Sep 17 00:00:00 2001 From: fireshoes Date: Tue, 18 Apr 2017 21:14:14 -0500 Subject: [PATCH 12/22] Added Split Card CMC tests for recent rule change. --- .../counterspell/CounterbalanceTest.java | 16 ++--- .../cost/splitcards/SplitCardCmcTest.java | 68 +++++++++++++++++++ 2 files changed, 76 insertions(+), 8 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/cost/splitcards/SplitCardCmcTest.java diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/counterspell/CounterbalanceTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/counterspell/CounterbalanceTest.java index 140fdaac94..c780a14710 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/counterspell/CounterbalanceTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/counterspell/CounterbalanceTest.java @@ -77,24 +77,24 @@ public class CounterbalanceTest extends CardTestPlayerBase { } /** - * Test that if the top card is a split card, both casting costs of the split cards + * Test that if the top card is a split card, the total of both halves of the split card * count to counter the spell. If one of the split cards halves has the equal casting - * cost, the spell is countered. + * cost, the spell is not countered. * */ @Test public void testSplitCard() { - addCard(Zone.HAND, playerA, "Typhoid Rats"); - addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); + addCard(Zone.HAND, playerA, "Nessian Courser"); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); addCard(Zone.BATTLEFIELD, playerB, "Counterbalance"); addCard(Zone.BATTLEFIELD, playerB, "Island", 2); - addCard(Zone.LIBRARY, playerB, "Wear // Tear"); // CMC 2 and 1 + addCard(Zone.LIBRARY, playerB, "Wear // Tear"); // CMC 3 skipInitShuffling(); // so the set to top card stays at top - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Typhoid Rats"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Nessian Courser"); setChoice(playerB, "Yes"); setStopAt(1, PhaseStep.BEGIN_COMBAT); @@ -103,8 +103,8 @@ public class CounterbalanceTest extends CardTestPlayerBase { assertLife(playerA, 20); assertLife(playerB, 20); - assertPermanentCount(playerA, "Typhoid Rats", 0); - assertGraveyardCount(playerA, "Typhoid Rats", 1); + assertPermanentCount(playerA, "Nessian Courser", 0); + assertGraveyardCount(playerA, "Nessian Courser", 1); assertGraveyardCount(playerA, 1); assertGraveyardCount(playerB, 0); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/splitcards/SplitCardCmcTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/splitcards/SplitCardCmcTest.java new file mode 100644 index 0000000000..6c7a4e80e4 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/splitcards/SplitCardCmcTest.java @@ -0,0 +1,68 @@ +package org.mage.test.cards.cost.splitcards; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author fireshoes + */ +public class SplitCardCmcTest extends CardTestPlayerBase { + + /** + * The core of the change is that we're no longer assuming split cards have two sets of characteristics + * when they're not on the stack. Some characteristics have multiple pieces of information very naturally + * —Destined to Lead is an instant sorcery, the same as Ornithopter is an artifact creature. It's black and green + * just like Winding Constrictor because its mana cost has B and G in it. Continuing that, the mana cost combines + * the components, and a card asking for Destined to Lead's mana cost sees 4BG. + * + * So now, the converted mana cost question is simple: if Destined to Lead isn't on the stack, it has a converted mana cost + * of 6. Destined on the stack is still a black instant with a converted mana cost of 2, and Lead on the stack is still a + * green sorcery with a converted mana cost of 4, but Destined to Lead, any time it's not one or the other, is a black and green + * instant sorcery with a converted mana cost of 6. + */ + + @Test + public void testSplitCardCmcInHand() { + // Total CMC of Failure // Comply is 3, so should be exiled by Transgress the Mind. + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + addCard(Zone.HAND, playerA, "Transgress the Mind"); + addCard(Zone.HAND, playerB, "Failure // Comply"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Transgress the Mind", playerB); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertExileCount("Failure // Comply", 1); + } + + @Test + public void testSplitCardCmcOnStack() { + // Counterbalance revealing Wear // Tear counters a spell with converted mana cost 3, but not 1 or 2. + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); + addCard(Zone.HAND, playerA, "Typhoid Rats"); + + addCard(Zone.BATTLEFIELD, playerB, "Counterbalance"); + addCard(Zone.BATTLEFIELD, playerB, "Island", 2); + + addCard(Zone.LIBRARY, playerB, "Wear // Tear"); // CMC now 3 + skipInitShuffling(); // so the set to top card stays at top + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Typhoid Rats"); + setChoice(playerB, "Yes"); // Reveal to Counterbalance to attempt to counter Typhoid Rats + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertLife(playerA, 20); + assertLife(playerB, 20); + + assertPermanentCount(playerA, "Typhoid Rats", 1); + assertGraveyardCount(playerA, "Typhoid Rats", 0); + assertGraveyardCount(playerA, 0); + assertGraveyardCount(playerB, 0); + } + +} From 513f5dfb7d0f8ecf772fd691ac4f049069aa426e Mon Sep 17 00:00:00 2001 From: drmDev Date: Tue, 18 Apr 2017 23:36:00 -0400 Subject: [PATCH 13/22] UT confirming bug #3167 --- .../cards/single/emn/PermeatingMassTest.java | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/emn/PermeatingMassTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/emn/PermeatingMassTest.java index e462243772..da14227d55 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/emn/PermeatingMassTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/emn/PermeatingMassTest.java @@ -12,7 +12,7 @@ import org.mage.test.serverside.base.CardTestPlayerBase; /** * - * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) + * @author escplan9 */ public class PermeatingMassTest extends CardTestPlayerBase { @@ -35,4 +35,38 @@ public class PermeatingMassTest extends CardTestPlayerBase { assertPermanentCount(playerA, "Permeating Mass", 1); assertPowerToughness(playerA, "Permeating Mass", 1, 3); } + + @Test + public void damagedCreatureWithVaryingPTbecomesCopyOfPermeatingMass() { + /* + Permeating Mass {G} + Creature — Spirit - 1/3 + Whenever Permeating Mass deals combat damage to a creature, that creature becomes a copy of Permeating Mass. + */ + String pMass = "Permeating Mass"; + + /* + Dungrove Elder {2}{G} + Creature — Treefolk + Hexproof * / * + Dungrove Elder's power and toughness are each equal to the number of Forests you control. + */ + String dElder = "Dungrove Elder"; + + addCard(Zone.BATTLEFIELD, playerA, pMass); + addCard(Zone.BATTLEFIELD, playerB, "Forest", 4); + addCard(Zone.BATTLEFIELD, playerB, dElder); // 4/4 with the 4 forests + + attack(2, playerB, dElder); + block(2, playerA, pMass, dElder); + + setStopAt(2, PhaseStep.END_COMBAT); + execute(); + + assertLife(playerA, 20); + assertLife(playerB, 20); + assertGraveyardCount(playerA, pMass, 1); + assertPermanentCount(playerB, pMass, 1); // dungrove elder becomes copy of permeating mass + assertPowerToughness(playerB, "Permeating Mass", 1, 3); // and should have P/T 1/3 + } } From cb70af6f504061494d21872eafb4c7eadb0bf13d Mon Sep 17 00:00:00 2001 From: drmDev Date: Tue, 18 Apr 2017 23:38:08 -0400 Subject: [PATCH 14/22] UT confirming bug for #3165 --- .../test/cards/planeswalker/LilianaTest.java | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/planeswalker/LilianaTest.java diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/planeswalker/LilianaTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/planeswalker/LilianaTest.java new file mode 100644 index 0000000000..2913683dc4 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/planeswalker/LilianaTest.java @@ -0,0 +1,65 @@ +package org.mage.test.cards.planeswalker; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author escplan9 + */ +public class LilianaTest extends CardTestPlayerBase { + + @Test + public void testMe() { + /* + Binding Mummy {1}{W} + Creature - Zombie 2/2 + Whenever another Zombie enters the battlefield under your control, you may tap target artifact or creature. + */ + String bMummy = "Binding Mummy"; + + /* + Liliana, Death's Majesty {3}{B}{B} + Planeswalker — Liliana 5 loyalty + [+1] : Create a 2/2 black Zombie creature token. Put the top two cards of your library into your graveyard. + [-3] : Return target creature card from your graveyard to the battlefield. That creature is a black Zombie in addition to its other colors and types. + [-7] : Destroy all non-Zombie creatures. + */ + String liliannaDM = "Liliana, Death's Majesty"; + + /* + Winged Shepherd {5}{W} + Creature - Angel 3/3 + Flying, vigilance + Cycling {W} + */ + String wShepherd = "Winged Shepherd"; + + String yOx = "Yoked Ox"; // {W} 0/4 + + addCard(Zone.BATTLEFIELD, playerA, bMummy); + addCard(Zone.HAND, playerA, liliannaDM); + addCard(Zone.GRAVEYARD, playerA, wShepherd); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5); + addCard(Zone.BATTLEFIELD, playerB, yOx); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, liliannaDM); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "-3:"); // Liliana -3 + addTarget(playerA, wShepherd); // returns to battlefield and become zombie on top of other types + setChoice(playerA, "Yes"); // use Binding Mummy ability + addTarget(playerA, yOx); // tap the ox + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, bMummy, 1); + assertPermanentCount(playerA, liliannaDM, 1); + assertPermanentCount(playerA, wShepherd, 1); + assertPermanentCount(playerB, yOx, 1); + assertCounterCount(playerA, liliannaDM, CounterType.LOYALTY, 2); + assertTapped(yOx, true); + } +} From 97d60a940c2e7b7dfb3ec7401472e76f2c5398c7 Mon Sep 17 00:00:00 2001 From: drmDev Date: Tue, 18 Apr 2017 23:40:19 -0400 Subject: [PATCH 15/22] add type checking --- .../java/org/mage/test/cards/planeswalker/LilianaTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/planeswalker/LilianaTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/planeswalker/LilianaTest.java index 2913683dc4..ce6452da54 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/planeswalker/LilianaTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/planeswalker/LilianaTest.java @@ -1,5 +1,6 @@ package org.mage.test.cards.planeswalker; +import mage.constants.CardType; import mage.constants.PhaseStep; import mage.constants.Zone; import mage.counters.CounterType; @@ -60,6 +61,8 @@ public class LilianaTest extends CardTestPlayerBase { assertPermanentCount(playerA, wShepherd, 1); assertPermanentCount(playerB, yOx, 1); assertCounterCount(playerA, liliannaDM, CounterType.LOYALTY, 2); + assertType(wShepherd, CardType.CREATURE, "Zombie"); // should have subtype zombie on top of angel type + assertType(wShepherd, CardType.CREATURE, "Angel"); assertTapped(yOx, true); } } From 58b1c4c0c8a0d96c2a30cbcd944a9e18e69ebd3d Mon Sep 17 00:00:00 2001 From: ingmargoudt Date: Wed, 19 Apr 2017 12:15:36 +0200 Subject: [PATCH 16/22] small fix on color --- Mage.Sets/src/mage/cards/w/WanderingFumarole.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/cards/w/WanderingFumarole.java b/Mage.Sets/src/mage/cards/w/WanderingFumarole.java index 3aae09c7ae..fa8d76ffbf 100644 --- a/Mage.Sets/src/mage/cards/w/WanderingFumarole.java +++ b/Mage.Sets/src/mage/cards/w/WanderingFumarole.java @@ -27,7 +27,6 @@ */ package mage.cards.w; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.EntersBattlefieldTappedAbility; import mage.abilities.common.SimpleActivatedAbility; @@ -44,6 +43,8 @@ import mage.constants.Duration; import mage.constants.Zone; import mage.game.permanent.token.Token; +import java.util.UUID; + /** * * @author fireshoes @@ -84,7 +85,7 @@ class WanderingFumaroleToken extends Token { cardType.add(CardType.CREATURE); subtype.add("Elemental"); color.setRed(true); - color.setWhite(true); + color.setBlue(true); power = new MageInt(1); toughness = new MageInt(4); addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new SwitchPowerToughnessSourceEffect(Duration.EndOfTurn), new ManaCostsImpl("{0}"))); From 0b827b239c936c1e40d21df10c772352398c9d9a Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Wed, 19 Apr 2017 12:36:46 +0200 Subject: [PATCH 17/22] * Fixed the handling of comparing characteristics concerning split cards (related to the recent rule changes). --- .../abilities/keywords/TransmuteTest.java | 36 +++++++++++++++++-- .../cost/splitcards/SplitCardCmcTest.java | 10 +++++- .../test/cards/single/akh/DuskDawnTest.java | 8 +++-- .../base/impl/CardTestPlayerAPIImpl.java | 16 +++++++-- .../src/main/java/mage/filter/FilterCard.java | 10 +----- Mage/src/main/java/mage/util/CardUtil.java | 18 +++------- 6 files changed, 67 insertions(+), 31 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/TransmuteTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/TransmuteTest.java index d275bc45a6..ebaf77092e 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/TransmuteTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/TransmuteTest.java @@ -73,18 +73,48 @@ public class TransmuteTest extends CardTestPlayerBase { @Test public void searchSplittedCardOneManaCmcSpell() { addCard(Zone.BATTLEFIELD, playerA, "Island", 3); - addCard(Zone.HAND, playerA, "Dizzy Spell"); + // Target creature gets -3/-0 until end of turn. + // Transmute {1}{U}{U} ({1}{U}{U}, Discard this card: Search your library for a card with the same converted mana cost as this card, reveal it, and put it into your hand. Then shuffle your library. Transmute only as a sorcery.) + addCard(Zone.HAND, playerA, "Dizzy Spell"); // Instant {U} + // Wear {1}{R} + // Destroy target artifact. + // Tear {W} + // Destroy target enchantment. addCard(Zone.LIBRARY, playerA, "Wear // Tear"); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Transmute {1}{U}{U}"); + setChoice(playerA, "Wear // Tear"); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); assertGraveyardCount(playerA, "Dizzy Spell", 1); - assertHandCount(playerA, "Wear", 1); // Filter search can only search for one side of a split card - assertHandCount(playerA, "Tear", 1); // Filter search can only search for one side of a split card + assertHandCount(playerA, "Wear // Tear", 0); + } + + @Test + public void searchSplittedCardThreeManaCmcSpell() { + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); + // Counter target spell unless its controller discards his or her hand. + // Transmute {1}{U}{B} + addCard(Zone.HAND, playerA, "Perplex"); // Instant {1}{U}{B} + + // Wear {1}{R} + // Destroy target artifact. + // Tear {W} + // Destroy target enchantment. + addCard(Zone.LIBRARY, playerA, "Wear // Tear"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Transmute {1}{U}{B}"); + setChoice(playerA, "Wear // Tear"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Perplex", 1); + assertHandCount(playerA, "Wear // Tear", 1); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/splitcards/SplitCardCmcTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/splitcards/SplitCardCmcTest.java index 6c7a4e80e4..0d18400e61 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/splitcards/SplitCardCmcTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/splitcards/SplitCardCmcTest.java @@ -28,6 +28,8 @@ public class SplitCardCmcTest extends CardTestPlayerBase { public void testSplitCardCmcInHand() { // Total CMC of Failure // Comply is 3, so should be exiled by Transgress the Mind. addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + // Devoid + // Target player reveals his or her hand. You may choose a card from it with converted mana cost 3 or greater and exile that card. addCard(Zone.HAND, playerA, "Transgress the Mind"); addCard(Zone.HAND, playerB, "Failure // Comply"); @@ -42,11 +44,17 @@ public class SplitCardCmcTest extends CardTestPlayerBase { public void testSplitCardCmcOnStack() { // Counterbalance revealing Wear // Tear counters a spell with converted mana cost 3, but not 1 or 2. addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); - addCard(Zone.HAND, playerA, "Typhoid Rats"); + addCard(Zone.HAND, playerA, "Typhoid Rats"); // Creature 1/1 {B} + // Whenever an opponent casts a spell, you may reveal the top card of your library. If you do, counter that spell + // if it has the same converted mana cost as the revealed card. addCard(Zone.BATTLEFIELD, playerB, "Counterbalance"); addCard(Zone.BATTLEFIELD, playerB, "Island", 2); + // Wear {1}{R} + // Destroy target artifact. + // Tear {W} + // Destroy target enchantment. addCard(Zone.LIBRARY, playerB, "Wear // Tear"); // CMC now 3 skipInitShuffling(); // so the set to top card stays at top diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/akh/DuskDawnTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/akh/DuskDawnTest.java index 6dd662ff13..067f5c040d 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/akh/DuskDawnTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/akh/DuskDawnTest.java @@ -65,12 +65,16 @@ public class DuskDawnTest extends CardTestPlayerBase { assertGraveyardCount(playerA, "Dusk // Dawn", 0); } + // Fail to cast Dawn (Aftermath part) from hand @Test public void testCastDawnFail() { - // Fail to cast dawn from hand + // Dusk {2}{W}{W} + // Destroy all creatures with power 3 or greater. + // Dawn {3}{W}{W} + // Return all creature cards with power less than or equal to 2 from your graveyard to your hand. addCard(Zone.HAND, playerA, "Dusk // Dawn"); addCard(Zone.BATTLEFIELD, playerA, "Plains", 5); - addCard(Zone.GRAVEYARD, playerA, "Devoted Hero"); + addCard(Zone.GRAVEYARD, playerA, "Devoted Hero"); // Creature 1/2 {W} castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Dawn"); setStopAt(1, PhaseStep.END_TURN); diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java index cabd79c459..0d3b5adf83 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java @@ -805,9 +805,19 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param count Expected count. */ public void assertHandCount(Player player, String cardName, int count) throws AssertionError { - FilterCard filter = new FilterCard(); - filter.add(new NamePredicate(cardName)); - int actual = currentGame.getPlayer(player.getId()).getHand().count(filter, player.getId(), currentGame); + int actual; + if (cardName.contains("//")) { // special logic for cheched split cards, because in game logic of card name filtering is different than for test + actual = 0; + for (Card card : currentGame.getPlayer(player.getId()).getHand().getCards(currentGame)) { + if (card.getName().equals(cardName)) { + actual++; + } + } + } else { + FilterCard filter = new FilterCard(); + filter.add(new NamePredicate(cardName)); + actual = currentGame.getPlayer(player.getId()).getHand().count(filter, player.getId(), currentGame); + } Assert.assertEquals("(Hand) Card counts for card " + cardName + " for " + player.getName() + " are not equal ", count, actual); } diff --git a/Mage/src/main/java/mage/filter/FilterCard.java b/Mage/src/main/java/mage/filter/FilterCard.java index b1d25603e8..ceff64ce94 100644 --- a/Mage/src/main/java/mage/filter/FilterCard.java +++ b/Mage/src/main/java/mage/filter/FilterCard.java @@ -28,14 +28,11 @@ package mage.filter; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; - import mage.cards.Card; -import mage.cards.SplitCard; import mage.filter.predicate.ObjectPlayer; import mage.filter.predicate.ObjectPlayerPredicate; import mage.filter.predicate.ObjectSourcePlayer; @@ -78,12 +75,7 @@ public class FilterCard extends FilterObject { if (card == null) { return false; } - if (card.isSplitCard()) { - return super.match(((SplitCard) card).getLeftHalfCard(), game) - || super.match(((SplitCard) card).getRightHalfCard(), game); - } else { - return super.match(card, game); - } + return super.match(card, game); } public boolean match(Card card, UUID playerId, Game game) { diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index a431a616f6..2a89380ec7 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -27,6 +27,10 @@ */ package mage.util; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; import mage.MageObject; import mage.Mana; import mage.ObjectColor; @@ -36,7 +40,6 @@ import mage.abilities.SpellAbility; import mage.abilities.costs.VariableCost; import mage.abilities.costs.mana.*; import mage.cards.Card; -import mage.cards.SplitCard; import mage.filter.FilterMana; import mage.game.Game; import mage.game.permanent.Permanent; @@ -44,11 +47,6 @@ import mage.game.permanent.token.Token; import mage.game.stack.Spell; import mage.util.functions.CopyTokenFunction; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; - /** * @author nantuko */ @@ -540,13 +538,7 @@ public final class CardUtil { cmcObject.add(object.getConvertedManaCost()); } else if (object instanceof Card) { Card card = (Card) object; - if (card instanceof SplitCard) { - SplitCard splitCard = (SplitCard) card; - cmcObject.add(splitCard.getLeftHalfCard().getConvertedManaCost()); - cmcObject.add(splitCard.getRightHalfCard().getConvertedManaCost()); - } else { - cmcObject.add(card.getConvertedManaCost()); - } + cmcObject.add(card.getConvertedManaCost()); } return cmcObject; } From 04c80c27a36ce3614c41656f009c60a246675db2 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Wed, 19 Apr 2017 16:40:00 +0200 Subject: [PATCH 18/22] * Fixed Binding Mummy's ability doesn't trigger from Liliana, Death Majesty -3 ability (fixes #3165). --- Mage.Sets/src/mage/cards/d/DungroveElder.java | 8 +++++--- Mage.Sets/src/mage/cards/l/LilianaDeathsMajesty.java | 10 ++++------ .../continuous/BecomesBlackZombieAdditionEffect.java | 5 ++++- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/Mage.Sets/src/mage/cards/d/DungroveElder.java b/Mage.Sets/src/mage/cards/d/DungroveElder.java index a7833ce80d..25c9ab1f1f 100644 --- a/Mage.Sets/src/mage/cards/d/DungroveElder.java +++ b/Mage.Sets/src/mage/cards/d/DungroveElder.java @@ -28,6 +28,7 @@ package mage.cards.d; +import java.util.UUID; import mage.MageInt; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; @@ -41,8 +42,6 @@ import mage.constants.Zone; import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.mageobject.SubtypePredicate; -import java.util.UUID; - /** * * @author Loki @@ -62,7 +61,10 @@ public class DungroveElder extends CardImpl { this.power = new MageInt(0); this.toughness = new MageInt(0); - this.addAbility(HexproofAbility.getInstance()); + // Hexproof (This creature can't be the target of spells or abilities your opponents control.) + this.addAbility(HexproofAbility.getInstance()); + + // Dungrove Elder's power and toughness are each equal to the number of Forests you control. this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetPowerToughnessSourceEffect(new PermanentsOnBattlefieldCount(filterLands), Duration.EndOfGame))); } diff --git a/Mage.Sets/src/mage/cards/l/LilianaDeathsMajesty.java b/Mage.Sets/src/mage/cards/l/LilianaDeathsMajesty.java index d8d044b6d4..ab62477845 100644 --- a/Mage.Sets/src/mage/cards/l/LilianaDeathsMajesty.java +++ b/Mage.Sets/src/mage/cards/l/LilianaDeathsMajesty.java @@ -28,10 +28,8 @@ package mage.cards.l; import java.util.UUID; - import mage.abilities.LoyaltyAbility; import mage.abilities.common.PlanswalkerEntersWithLoyalityCountersAbility; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.DestroyAllEffect; import mage.abilities.effects.common.PutTopCardOfLibraryIntoGraveControllerEffect; @@ -40,8 +38,6 @@ import mage.abilities.effects.common.continuous.BecomesBlackZombieAdditionEffect import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.Outcome; import mage.filter.common.FilterCreatureCard; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicates; @@ -75,9 +71,11 @@ public class LilianaDeathsMajesty extends CardImpl { this.addAbility(ability); // -3: Return target creature card from your graveyard to the battlefield. That creature is a black Zombie in addition to its other colors and types. - ability = new LoyaltyAbility(new ReturnFromGraveyardToBattlefieldTargetEffect(), -3); + ability = new LoyaltyAbility(new BecomesBlackZombieAdditionEffect() // because the effect has to be active for triggered effects that e.g. check if the creature entering is a Zombie, the continuous effect needs to be added before the card moving effect is applied + .setText(""), -3); ability.addTarget(new TargetCardInYourGraveyard(new FilterCreatureCard("creature card from your graveyard"))); - ability.addEffect(new BecomesBlackZombieAdditionEffect()); + ability.addEffect(new ReturnFromGraveyardToBattlefieldTargetEffect() + .setText("Return target creature card from your graveyard to the battlefield. That creature is a black Zombie in addition to its other colors and types")); this.addAbility(ability); // -7: Destroy all non-Zombie creatures. diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesBlackZombieAdditionEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesBlackZombieAdditionEffect.java index c78390b299..0030052e48 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesBlackZombieAdditionEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesBlackZombieAdditionEffect.java @@ -58,7 +58,10 @@ public class BecomesBlackZombieAdditionEffect extends ContinuousEffectImpl { @Override public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { - Permanent creature = game.getPermanent(targetPointer.getFirst(game, source)); + Permanent creature = game.getPermanent(source.getTargets().getFirstTarget()); + if (creature == null) { + creature = game.getPermanentEntering(source.getTargets().getFirstTarget()); + } if (creature != null) { switch (layer) { case TypeChangingEffects_4: From 10a043f19bb87843da446b04a751602d2521c315 Mon Sep 17 00:00:00 2001 From: Jeff Date: Wed, 19 Apr 2017 11:02:27 -0500 Subject: [PATCH 19/22] - Fixed Soul-Scar Mage. Bug #3163 --- Mage.Sets/src/mage/cards/s/SoulScarMage.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Mage.Sets/src/mage/cards/s/SoulScarMage.java b/Mage.Sets/src/mage/cards/s/SoulScarMage.java index 4f12aa3ba9..10001505fe 100644 --- a/Mage.Sets/src/mage/cards/s/SoulScarMage.java +++ b/Mage.Sets/src/mage/cards/s/SoulScarMage.java @@ -56,7 +56,7 @@ public class SoulScarMage extends CardImpl { public SoulScarMage(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R}"); - + this.subtype.add("Human"); this.subtype.add("Wizard"); this.power = new MageInt(1); @@ -114,8 +114,14 @@ class SoulScarMageDamageReplacementEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { - boolean weControlSource = game.getControllerId(event.getSourceId()).equals(source.getControllerId()); - boolean isNoncombatDamage = !((DamageCreatureEvent)event).isCombatDamage(); - return weControlSource && isNoncombatDamage; + UUID sourceControllerId = game.getControllerId(event.getSourceId()); + UUID targetControllerId = game.getControllerId(event.getTargetId()); + UUID controllerId = source.getControllerId(); + boolean weControlSource = controllerId == sourceControllerId; + boolean opponentControlsTarget = game.getOpponents(sourceControllerId).contains(targetControllerId); + boolean isNoncombatDamage = !((DamageCreatureEvent) event).isCombatDamage(); + return weControlSource + && isNoncombatDamage + && opponentControlsTarget; } } From ad3e5749f5ac9b30d211823b91f4c8906fa1080b Mon Sep 17 00:00:00 2001 From: fireshoes Date: Wed, 19 Apr 2017 11:46:20 -0500 Subject: [PATCH 20/22] Added Planeswalker Decks to Sample Decks. --- .../Ajani, Valiant Protector.dck | 27 +++++++++++++++++++ .../Planeswalk Decks/Chandra, Pyrogenius.dck | 27 +++++++++++++++++++ .../Gideon, Martial Paragon.dck | 24 +++++++++++++++++ .../Liliana, Death Wielder.dck | 26 ++++++++++++++++++ .../Nissa, Nature's Artisan.dck | 24 +++++++++++++++++ .../Tezzeret, Master of Metal.dck | 27 +++++++++++++++++++ 6 files changed, 155 insertions(+) create mode 100644 Mage.Client/release/sample-decks/Planeswalk Decks/Ajani, Valiant Protector.dck create mode 100644 Mage.Client/release/sample-decks/Planeswalk Decks/Chandra, Pyrogenius.dck create mode 100644 Mage.Client/release/sample-decks/Planeswalk Decks/Gideon, Martial Paragon.dck create mode 100644 Mage.Client/release/sample-decks/Planeswalk Decks/Liliana, Death Wielder.dck create mode 100644 Mage.Client/release/sample-decks/Planeswalk Decks/Nissa, Nature's Artisan.dck create mode 100644 Mage.Client/release/sample-decks/Planeswalk Decks/Tezzeret, Master of Metal.dck diff --git a/Mage.Client/release/sample-decks/Planeswalk Decks/Ajani, Valiant Protector.dck b/Mage.Client/release/sample-decks/Planeswalk Decks/Ajani, Valiant Protector.dck new file mode 100644 index 0000000000..2ffcb886d6 --- /dev/null +++ b/Mage.Client/release/sample-decks/Planeswalk Decks/Ajani, Valiant Protector.dck @@ -0,0 +1,27 @@ +3 [AER:173] Renegade Map +2 [KLD:144] Armorcraft Judge +9 [KLD:263] Forest +1 [KLD:181] Engineered Might +1 [AER:149] Daredevil Dragster +1 [AER:105] Aid from the Cowl +3 [AER:7] Audacious Infiltrator +1 [AER:5] Airdrop Aeronauts +4 [AER:189] Tranquil Expanse +1 [AER:22] Solemn Recruit +2 [AER:126] Unbridled Growth +2 [AER:125] Silkweaver Elite +1 [AER:120] Prey Upon +4 [AER:186] Inspiring Roar +1 [AER:185] Ajani, Valiant Protector +2 [AER:188] Ajani's Aid +3 [AER:187] Ajani's Comrade +1 [AER:121] Ridgescale Tusker +9 [KLD:250] Plains +1 [KLD:156] Ghirapur Guide +2 [AER:180] Verdant Automaton +1 [AER:15] Deadeye Harpooner +2 [AER:117] Narnam Renegade +1 [AER:118] Natural Obsolescence +2 [AER:113] Lifecraft Cavalry +LAYOUT MAIN:(1,7)(CMC,false,50)|([KLD:263],[KLD:263],[KLD:263],[KLD:263],[KLD:263],[KLD:263],[KLD:263],[KLD:263],[KLD:263],[KLD:250],[KLD:250],[KLD:250],[KLD:250],[KLD:250],[KLD:250],[KLD:250],[KLD:250],[KLD:250],[AER:189],[AER:189],[AER:189],[AER:189])([AER:117],[AER:117],[AER:120],[AER:173],[AER:173],[AER:173],[AER:126],[AER:126])([AER:187],[AER:187],[AER:187],[AER:7],[AER:7],[AER:7],[AER:118],[AER:180],[AER:180])([AER:149],[AER:15],[KLD:156],[AER:125],[AER:125],[AER:22])([AER:188],[AER:188],[KLD:144],[KLD:144],[AER:186],[AER:186],[AER:186],[AER:186])([AER:105],[AER:5],[KLD:181],[AER:113],[AER:113],[AER:121])([AER:185]) +LAYOUT SIDEBOARD:(0,0)(NONE,false,50)| diff --git a/Mage.Client/release/sample-decks/Planeswalk Decks/Chandra, Pyrogenius.dck b/Mage.Client/release/sample-decks/Planeswalk Decks/Chandra, Pyrogenius.dck new file mode 100644 index 0000000000..29dd1db50a --- /dev/null +++ b/Mage.Client/release/sample-decks/Planeswalk Decks/Chandra, Pyrogenius.dck @@ -0,0 +1,27 @@ +11 [KLD:261] Mountain +1 [KLD:265] Chandra, Pyrogenius +2 [KLD:188] Veteran Motorist +4 [KLD:266] Flame Lash +1 [KLD:28] Skyswirl Harrier +1 [KLD:225] Ovalchase Dragster +2 [KLD:267] Liberating Combustion +3 [KLD:268] Renegade Firebrand +4 [AKH:274] Stone Quarry +1 [KLD:109] Cathartic Reunion +1 [KLD:107] Brazen Scourge +10 [KLD:250] Plains +2 [KLD:133] Spireside Infiltrator +2 [KLD:233] Sky Skiff +2 [KLD:230] Renegade Freighter +1 [KLD:132] Speedway Fanatic +2 [KLD:33] Trusty Companion +1 [KLD:198] Bomat Bazaar Barge +1 [KLD:16] Gearshift Ace +2 [KLD:2] Aerial Responder +2 [KLD:7] Built to Last +1 [KLD:236] Snare Thopter +1 [KLD:214] Fleetwheel Cruiser +1 [KLD:114] Fateful Showdown +1 [KLD:238] Weldfast Monitor +LAYOUT MAIN:(1,7)(CMC,false,50)|([KLD:261],[KLD:261],[KLD:261],[KLD:261],[KLD:261],[KLD:261],[KLD:261],[KLD:261],[KLD:261],[KLD:261],[KLD:261],[KLD:250],[KLD:250],[KLD:250],[KLD:250],[KLD:250],[KLD:250],[KLD:250],[KLD:250],[KLD:250],[KLD:250],[AKH:274],[AKH:274],[AKH:274],[AKH:274])([KLD:7],[KLD:7])([KLD:109],[KLD:16],[KLD:233],[KLD:233],[KLD:132],[KLD:33],[KLD:33],[KLD:188],[KLD:188])([KLD:2],[KLD:2],[KLD:107],[KLD:268],[KLD:268],[KLD:268],[KLD:230],[KLD:230],[KLD:133],[KLD:133],[KLD:238])([KLD:198],[KLD:114],[KLD:266],[KLD:266],[KLD:266],[KLD:266],[KLD:214],[KLD:225],[KLD:236])([KLD:267],[KLD:267],[KLD:28])([KLD:265]) +LAYOUT SIDEBOARD:(0,0)(NONE,false,50)| diff --git a/Mage.Client/release/sample-decks/Planeswalk Decks/Gideon, Martial Paragon.dck b/Mage.Client/release/sample-decks/Planeswalk Decks/Gideon, Martial Paragon.dck new file mode 100644 index 0000000000..1b296855a2 --- /dev/null +++ b/Mage.Client/release/sample-decks/Planeswalk Decks/Gideon, Martial Paragon.dck @@ -0,0 +1,24 @@ +1 [AKH:270] Gideon, Martial Paragon +2 [AKH:272] Gideon's Resolve +10 [AKH:250] Plains +3 [AKH:271] Companion of the Trials +1 [AKH:10] Devoted Crop-Mate +1 [AKH:31] Tah-Crop Elite +2 [AKH:29] Sparring Mummy +1 [AKH:18] Impeccable Timing +3 [AKH:17] Gust Walker +1 [AKH:16] Glory-Bound Initiate +3 [AKH:117] Ahn-Crop Crasher +2 [AKH:139] Hyena Pack +2 [AKH:129] Electrify +1 [AKH:146] Pathmaker Initiate +2 [AKH:124] Cartouche of Zeal +2 [AKH:200] Honored Crop-Captain +1 [AKH:137] Hazoret's Favor +4 [AKH:274] Stone Quarry +4 [AKH:273] Graceful Cat +3 [AKH:152] Trial of Zeal +1 [AKH:144] Nef-Crop Entangler +10 [AKH:253] Mountain +LAYOUT MAIN:(1,6)(CMC,false,50)|([AKH:274],[AKH:274],[AKH:274],[AKH:274],[AKH:250],[AKH:250],[AKH:250],[AKH:250],[AKH:250],[AKH:250],[AKH:250],[AKH:250],[AKH:250],[AKH:250],[AKH:253],[AKH:253],[AKH:253],[AKH:253],[AKH:253],[AKH:253],[AKH:253],[AKH:253],[AKH:253],[AKH:253])([AKH:124],[AKH:124])([AKH:16],[AKH:17],[AKH:17],[AKH:17],[AKH:144],[AKH:146],[AKH:200],[AKH:200],[AKH:18])([AKH:271],[AKH:271],[AKH:271],[AKH:10],[AKH:273],[AKH:273],[AKH:273],[AKH:273],[AKH:117],[AKH:117],[AKH:117],[AKH:137],[AKH:152],[AKH:152],[AKH:152])([AKH:29],[AKH:29],[AKH:31],[AKH:139],[AKH:139],[AKH:129],[AKH:129])([AKH:270],[AKH:272],[AKH:272]) +LAYOUT SIDEBOARD:(0,0)(NONE,false,50)| diff --git a/Mage.Client/release/sample-decks/Planeswalk Decks/Liliana, Death Wielder.dck b/Mage.Client/release/sample-decks/Planeswalk Decks/Liliana, Death Wielder.dck new file mode 100644 index 0000000000..a559eca379 --- /dev/null +++ b/Mage.Client/release/sample-decks/Planeswalk Decks/Liliana, Death Wielder.dck @@ -0,0 +1,26 @@ +1 [AKH:160] Channeler Initiate +1 [AKH:91] Festering Mummy +1 [AKH:162] Crocodile of the Crossing +2 [AKH:89] Dune Beetle +2 [AKH:83] Cartouche of Ambition +2 [AKH:93] Gravedigger +2 [AKH:109] Splendid Agony +1 [AKH:226] Edifice of Authority +2 [AKH:79] Baleful Ammit +4 [AKH:278] Tattered Mummy +1 [AKH:234] Oracle's Vault +2 [AKH:113] Trial of Ambition +2 [AKH:277] Liliana's Influence +1 [AKH:167] Gift of Paradise +1 [AKH:244] Grasping Dunes +2 [AKH:158] Cartouche of Strength +4 [AKH:279] Foul Orchard +2 [AKH:197] Decimator Beetle +11 [AKH:252] Swamp +3 [AKH:276] Desiccated Naga +2 [AKH:166] Giant Spider +1 [AKH:232] Luxa River Shrine +9 [AKH:254] Forest +1 [AKH:275] Liliana, Death Wielder +LAYOUT MAIN:(1,8)(CMC,false,50)|([AKH:254],[AKH:254],[AKH:254],[AKH:254],[AKH:254],[AKH:254],[AKH:254],[AKH:254],[AKH:254],[AKH:279],[AKH:279],[AKH:279],[AKH:279],[AKH:244],[AKH:252],[AKH:252],[AKH:252],[AKH:252],[AKH:252],[AKH:252],[AKH:252],[AKH:252],[AKH:252],[AKH:252],[AKH:252])([AKH:91])([AKH:160],[AKH:89],[AKH:89],[AKH:278],[AKH:278],[AKH:278],[AKH:278],[AKH:113],[AKH:113])([AKH:79],[AKH:79],[AKH:83],[AKH:83],[AKH:158],[AKH:158],[AKH:276],[AKH:276],[AKH:276],[AKH:226],[AKH:167],[AKH:232],[AKH:109],[AKH:109])([AKH:162],[AKH:166],[AKH:166],[AKH:93],[AKH:93],[AKH:234])([AKH:197],[AKH:197])([AKH:277],[AKH:277])([AKH:275]) +LAYOUT SIDEBOARD:(0,0)(NONE,false,50)| diff --git a/Mage.Client/release/sample-decks/Planeswalk Decks/Nissa, Nature's Artisan.dck b/Mage.Client/release/sample-decks/Planeswalk Decks/Nissa, Nature's Artisan.dck new file mode 100644 index 0000000000..e67fcf5bf2 --- /dev/null +++ b/Mage.Client/release/sample-decks/Planeswalk Decks/Nissa, Nature's Artisan.dck @@ -0,0 +1,24 @@ +4 [KLD:272] Terrain Elemental +2 [KLD:273] Verdant Crescendo +2 [KLD:141] Appetite for the Unnatural +1 [KLD:270] Nissa, Nature's Artisan +2 [KLD:171] Thriving Rhino +2 [KLD:161] Longtusk Cub +3 [KLD:271] Guardian of the Great Conduit +2 [KLD:53] Janjeet Sentry +1 [KLD:167] Riparian Tiger +3 [KLD:145] Attune with Aether +2 [KLD:54] Long-Finned Skywhale +2 [KLD:142] Arborback Stomper +4 [KLD:274] Woodland Stream +11 [KLD:263] Forest +2 [KLD:66] Thriving Turtle +2 [KLD:55] Malfunction +8 [KLD:253] Island +2 [KLD:180] Empyreal Voyager +1 [KLD:39] Aethersquall Ancient +1 [KLD:168] Sage of Shaila's Claim +2 [KLD:169] Servant of the Conduit +1 [KLD:147] Bristling Hydra +LAYOUT MAIN:(1,8)(CMC,false,50)|([KLD:263],[KLD:263],[KLD:263],[KLD:263],[KLD:263],[KLD:263],[KLD:263],[KLD:263],[KLD:263],[KLD:263],[KLD:263],[KLD:253],[KLD:253],[KLD:253],[KLD:253],[KLD:253],[KLD:253],[KLD:253],[KLD:253],[KLD:274],[KLD:274],[KLD:274],[KLD:274])([KLD:145],[KLD:145],[KLD:145],[KLD:66],[KLD:66])([KLD:161],[KLD:161],[KLD:168],[KLD:169],[KLD:169],[KLD:272],[KLD:272],[KLD:272],[KLD:272])([KLD:141],[KLD:141],[KLD:180],[KLD:180],[KLD:53],[KLD:53],[KLD:171],[KLD:171])([KLD:147],[KLD:271],[KLD:271],[KLD:271],[KLD:54],[KLD:54],[KLD:55],[KLD:55],[KLD:273],[KLD:273])([KLD:142],[KLD:142],[KLD:167])([KLD:270])([KLD:39]) +LAYOUT SIDEBOARD:(0,0)(NONE,false,50)| diff --git a/Mage.Client/release/sample-decks/Planeswalk Decks/Tezzeret, Master of Metal.dck b/Mage.Client/release/sample-decks/Planeswalk Decks/Tezzeret, Master of Metal.dck new file mode 100644 index 0000000000..48ecec315f --- /dev/null +++ b/Mage.Client/release/sample-decks/Planeswalk Decks/Tezzeret, Master of Metal.dck @@ -0,0 +1,27 @@ +3 [AER:193] Tezzeret's Simulacrum +4 [AER:192] Pendulum of Patterns +1 [AER:151] Foundry Assembler +4 [AER:194] Submerged Boneyard +2 [AER:191] Tezzeret's Betrayal +1 [AER:190] Tezzeret, Master of Metal +1 [AER:42] Reverse Engineer +1 [AER:167] Ornithopter +1 [AER:65] Ironclad Revolutionary +1 [KLD:207] Dukhara Peafowl +1 [AER:163] Merchant's Dockhand +1 [AER:144] Barricade Breaker +2 [AER:143] Augmenting Automaton +1 [AER:41] Quicksmith Spy +2 [KLD:74] Dhund Operative +1 [AER:58] Fen Hauler +11 [KLD:253] Island +2 [AER:138] Tezzeret's Touch +10 [KLD:258] Swamp +2 [AER:178] Universal Solvent +2 [AER:156] Implement of Examination +2 [AER:50] Wind-Kin Raiders +1 [AER:177] Treasure Keeper +2 [AER:30] Bastion Inventor +1 [KLD:80] Essence Extraction +LAYOUT MAIN:(1,8)(CMC,false,50)|([KLD:253],[KLD:253],[KLD:253],[KLD:253],[KLD:253],[KLD:253],[KLD:253],[KLD:253],[KLD:253],[KLD:253],[KLD:253],[AER:167],[AER:194],[AER:194],[AER:194],[AER:194],[KLD:258],[KLD:258],[KLD:258],[KLD:258],[KLD:258],[KLD:258],[KLD:258],[KLD:258],[KLD:258],[KLD:258])([AER:143],[AER:143],[AER:163],[AER:178],[AER:178])([KLD:74],[KLD:74],[AER:192],[AER:192],[AER:192],[AER:192])([KLD:80],[AER:156],[AER:156],[AER:193],[AER:193],[AER:193],[AER:138],[AER:138])([KLD:207],[AER:41],[AER:177])([AER:151],[AER:42],[AER:191],[AER:191])([AER:30],[AER:30],[AER:65],[AER:190],[AER:50],[AER:50])([AER:144],[AER:58]) +LAYOUT SIDEBOARD:(0,0)(NONE,false,50)| From c5b3975700fa440eb5b834bcbe2ff14f19b7abda Mon Sep 17 00:00:00 2001 From: Jeff Date: Wed, 19 Apr 2017 15:05:23 -0500 Subject: [PATCH 21/22] - Changed == to equals(). --- Mage.Sets/src/mage/cards/s/SoulScarMage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/s/SoulScarMage.java b/Mage.Sets/src/mage/cards/s/SoulScarMage.java index 10001505fe..90c4c3d0dd 100644 --- a/Mage.Sets/src/mage/cards/s/SoulScarMage.java +++ b/Mage.Sets/src/mage/cards/s/SoulScarMage.java @@ -117,7 +117,7 @@ class SoulScarMageDamageReplacementEffect extends ReplacementEffectImpl { UUID sourceControllerId = game.getControllerId(event.getSourceId()); UUID targetControllerId = game.getControllerId(event.getTargetId()); UUID controllerId = source.getControllerId(); - boolean weControlSource = controllerId == sourceControllerId; + boolean weControlSource = controllerId.equals(sourceControllerId); boolean opponentControlsTarget = game.getOpponents(sourceControllerId).contains(targetControllerId); boolean isNoncombatDamage = !((DamageCreatureEvent) event).isCombatDamage(); return weControlSource From 1a464356647a9b9dfa2d98ad1a6fab29d7063ae8 Mon Sep 17 00:00:00 2001 From: Simown Date: Tue, 18 Apr 2017 22:20:07 +0100 Subject: [PATCH 22/22] Fixed blocking in the Unit Test Framework --- .../cards/continuous/TwoHeadedSliverTest.java | 11 +- .../requirement/BlockRequirementTest.java | 12 +- .../combat/CanBlockMultipleCreatures.java | 119 ---------- .../combat/CanBlockMultipleCreaturesTest.java | 223 ++++++++++++++++++ .../java/org/mage/test/player/TestPlayer.java | 104 ++++++-- 5 files changed, 319 insertions(+), 150 deletions(-) delete mode 100644 Mage.Tests/src/test/java/org/mage/test/combat/CanBlockMultipleCreatures.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/combat/CanBlockMultipleCreaturesTest.java diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/TwoHeadedSliverTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/TwoHeadedSliverTest.java index 98f1e49237..ba4a71c7e5 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/TwoHeadedSliverTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/TwoHeadedSliverTest.java @@ -5,6 +5,8 @@ import mage.constants.Zone; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; +import static junit.framework.TestCase.assertEquals; + /** * @author LevelX2 */ @@ -26,9 +28,12 @@ public class TwoHeadedSliverTest extends CardTestPlayerBase { block(3, playerB, "Silvercoat Lion", "Two-Headed Sliver"); setStopAt(3, PhaseStep.END_TURN); - execute(); - assertPermanentCount(playerA, "Two-Headed Sliver", 1); - assertLife(playerB, 19); + try { + execute(); + } catch (UnsupportedOperationException e) { + assertEquals("Two-Headed Sliver is blocked by 1 creature(s). It has to be blocked by 2 or more.", e.getMessage()); + } + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/requirement/BlockRequirementTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/requirement/BlockRequirementTest.java index 4305b94d51..27f1108e07 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/requirement/BlockRequirementTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/requirement/BlockRequirementTest.java @@ -32,6 +32,8 @@ import mage.constants.Zone; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; +import static junit.framework.TestCase.assertEquals; + /** * * @author LevelX2, icetc @@ -212,12 +214,12 @@ public class BlockRequirementTest extends CardTestPlayerBase { block(1, playerB, "Hill Giant", "Breaker of Armies"); setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); - execute(); - // Hill giant is still alive - assertPermanentCount(playerB, "Hill Giant", 1); - // Player B was unable to block, so goes down to 10 life - assertLife(playerB, 8); + try { + execute(); + } catch (UnsupportedOperationException e) { + assertEquals("Breaker of Armies is blocked by 1 creature(s). It has to be blocked by 2 or more.", e.getMessage()); + } } /* diff --git a/Mage.Tests/src/test/java/org/mage/test/combat/CanBlockMultipleCreatures.java b/Mage.Tests/src/test/java/org/mage/test/combat/CanBlockMultipleCreatures.java deleted file mode 100644 index abb44c24a2..0000000000 --- a/Mage.Tests/src/test/java/org/mage/test/combat/CanBlockMultipleCreatures.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * 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 org.mage.test.combat; - -import mage.constants.PhaseStep; -import mage.constants.Zone; -import org.junit.Ignore; -import org.junit.Test; -import org.mage.test.serverside.base.CardTestPlayerBase; - -/** - * - * @author jeffwadsworth - */ -public class CanBlockMultipleCreatures extends CardTestPlayerBase { - - // test must be ignored until creature blocking multiple supported by test framework - @Ignore - @Test - public void testCombat() { - addCard(Zone.BATTLEFIELD, playerA, "Watcher in the Web", 1); - - addCard(Zone.BATTLEFIELD, playerB, "Ulrich, Uncontested Alpha", 1); // 6/6 - addCard(Zone.BATTLEFIELD, playerB, "Kessig Dire Swine", 1); // 6/6 (trample if delirium) - addCard(Zone.BATTLEFIELD, playerB, "Howlpack Wolf", 1); // 3/3 - addCard(Zone.BATTLEFIELD, playerB, "Incorrigible Youths", 1); // 4/3 - - // Trample requirement for Kessig Dire Swine - addCard(Zone.GRAVEYARD, playerB, "Forest", 1); - addCard(Zone.GRAVEYARD, playerB, "Memnite", 1); - addCard(Zone.GRAVEYARD, playerB, "Flight", 1); - addCard(Zone.GRAVEYARD, playerB, "Drain Life", 1); - - // Attack with all 4 creatures and block all with the Watcher in the Web - attack(2, playerB, "Ulrich, Uncontested Alpha"); - attack(2, playerB, "Kessig Dire Swine"); - attack(2, playerB, "Howlpack Wolf"); - attack(2, playerB, "Incorrigible Youths"); - - block(2, playerA, "Watcher in the Web", "Ulrich, Uncontested Alpha"); - block(2, playerA, "Watcher in the Web", "Kessig Dire Swine"); - block(2, playerA, "Watcher in the Web", "Howlpack Wolf"); - block(2, playerA, "Watcher in the Web", "Incorrigible Youths"); - - setStopAt(2, PhaseStep.COMBAT_DAMAGE); - execute(); - - assertLife(playerA, 19); - - } - - /* - * Reported bug: Night Market Guard was able to block a creature with Menace - */ - @Test - public void testNightMarketGuardShouldNotBlockCreatureWithMenace() - { - /* - Night Market Guard {3} 3/1 - Artifact Creature — Construct - Night Market Guard can block an additional creature each combat. - */ - String nMarketGuard = "Night Market Guard"; - - /* - Embraal Bruiser {1}{B} - Creature - Human Warrior - Embraal Bruiser enters the battlefield tapped. - Embraal Bruiser has menace as long as you control an artifact. - */ - String eBruiser = "Embraal Bruiser"; - - /* - {0} 1/1 - * Artifact Creature — Construct - */ - String memnite = "Memnite"; - - addCard(Zone.BATTLEFIELD, playerA, nMarketGuard); - addCard(Zone.BATTLEFIELD, playerB, eBruiser); - addCard(Zone.BATTLEFIELD, playerB, memnite); // only here to grant Embraal Menace - - attack(4, playerB, eBruiser); - block(4, playerA, nMarketGuard, eBruiser); - - setStopAt(4, PhaseStep.POSTCOMBAT_MAIN); - execute(); - - assertTapped(eBruiser, true); - assertLife(playerA, 17); // could not block, so 3 damage goes through - assertPermanentCount(playerA, nMarketGuard, 1); - assertPermanentCount(playerB, eBruiser, 1); - } -} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/combat/CanBlockMultipleCreaturesTest.java b/Mage.Tests/src/test/java/org/mage/test/combat/CanBlockMultipleCreaturesTest.java new file mode 100644 index 0000000000..c488cb35f3 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/combat/CanBlockMultipleCreaturesTest.java @@ -0,0 +1,223 @@ +/* + * 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 org.mage.test.combat; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Ignore; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +import static org.junit.Assert.assertEquals; + +/** + * + * @author jeffwadsworth + * @author Simown + */ +public class CanBlockMultipleCreaturesTest extends CardTestPlayerBase { + + @Test + public void testMultipleBlockWithTrample() { + + addCard(Zone.BATTLEFIELD, playerA, "Watcher in the Web", 1); + + addCard(Zone.BATTLEFIELD, playerB, "Ulrich, Uncontested Alpha", 1); // 6/6 + addCard(Zone.BATTLEFIELD, playerB, "Kessig Dire Swine", 1); // 6/6 (trample if delirium) + addCard(Zone.BATTLEFIELD, playerB, "Howlpack Wolf", 1); // 3/3 + addCard(Zone.BATTLEFIELD, playerB, "Incorrigible Youths", 1); // 4/3 + + // Trample requirement for Kessig Dire Swine + addCard(Zone.GRAVEYARD, playerB, "Forest", 1); + addCard(Zone.GRAVEYARD, playerB, "Memnite", 1); + addCard(Zone.GRAVEYARD, playerB, "Flight", 1); + addCard(Zone.GRAVEYARD, playerB, "Drain Life", 1); + + // Attack with all 4 creatures and block all with the Watcher in the Web + attack(2, playerB, "Kessig Dire Swine"); + attack(2, playerB, "Ulrich, Uncontested Alpha"); + attack(2, playerB, "Howlpack Wolf"); + attack(2, playerB, "Incorrigible Youths"); + + // BLOCKING ORDER MATTERS - the trampling creature must be selected to block first + // You can manually change the blocking order but it's easier to assign them in order + block(2, playerA, "Watcher in the Web", "Kessig Dire Swine"); + block(2, playerA, "Watcher in the Web", "Ulrich, Uncontested Alpha"); + block(2, playerA, "Watcher in the Web", "Howlpack Wolf"); + block(2, playerA, "Watcher in the Web", "Incorrigible Youths"); + + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerA, 19); + + } + + @Test + public void testMultipleBlockWithTrample2() { + + addCard(Zone.BATTLEFIELD, playerA, "Watcher in the Web", 1); + + addCard(Zone.BATTLEFIELD, playerB, "Ulrich, Uncontested Alpha", 1); // 6/6 + addCard(Zone.BATTLEFIELD, playerB, "Kessig Dire Swine", 1); // 6/6 (trample if delirium) + addCard(Zone.BATTLEFIELD, playerB, "Howlpack Wolf", 1); // 3/3 + addCard(Zone.BATTLEFIELD, playerB, "Incorrigible Youths", 1); // 4/3 + + // Trample requirement for Kessig Dire Swine + addCard(Zone.GRAVEYARD, playerB, "Forest", 1); + addCard(Zone.GRAVEYARD, playerB, "Memnite", 1); + addCard(Zone.GRAVEYARD, playerB, "Flight", 1); + addCard(Zone.GRAVEYARD, playerB, "Drain Life", 1); + + // Attack with all 4 creatures and block all with the Watcher in the Web + attack(2, playerB, "Kessig Dire Swine"); + attack(2, playerB, "Ulrich, Uncontested Alpha"); + attack(2, playerB, "Howlpack Wolf"); + attack(2, playerB, "Incorrigible Youths"); + + // BLOCKING ORDER MATTERS - the trampling creature must be selected to block first + block(2, playerA, "Watcher in the Web", "Kessig Dire Swine"); + block(2, playerA, "Watcher in the Web", "Ulrich, Uncontested Alpha"); + block(2, playerA, "Watcher in the Web", "Howlpack Wolf"); + // Don't block Incorrigible Youths + + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + // Damage 1 from Kessig Dire Swine + 4 from Incorrigible Youths + assertLife(playerA, 15); + } + + @Test + public void testCanOnlyBlockSingle() { + + // Hundred-Handed One {2}{W}{W} + // Monstrosity 3. {3}{W}{W}{W} (If this creature isn’t monstrous, put three +1/+1 counters on it and it becomes monstrous.) + //As long as Hundred-Handed One is monstrous, it has reach and can block an additional ninety-nine creatures each combat. + addCard(Zone.BATTLEFIELD, playerA, "Hundred-Handed One", 1); + + addCard(Zone.BATTLEFIELD, playerB, "Bronze Sable", 1); // 2/1 + addCard(Zone.BATTLEFIELD, playerB, "Fabled Hero", 1); // 2/2 double strike + + // Attack with all 4 creatures and try and block both with hundred-handed one + attack(2, playerB, "Bronze Sable"); + attack(2, playerB, "Fabled Hero"); + + block(2, playerA, "Hundred-Handed One", "Bronze Sable"); + block(2, playerA, "Hundred-Handed One", "Fabled Hero"); + + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + + // Will fail on purpose - we are trying to block too many creatures! + try { + execute(); + } catch(UnsupportedOperationException e) { + assertEquals("Hundred-Handed One cannot block Fabled Hero", e.getMessage()); + } + } + + @Test + public void testCanBlockMultiple() { + + // Hundred-Handed One {2}{W}{W} + // Monstrosity 3. {3}{W}{W}{W} (If this creature isn’t monstrous, put three +1/+1 counters on it and it becomes monstrous.) + // As long as Hundred-Handed One is monstrous, it has reach and can block an additional ninety-nine creatures each combat. + addCard(Zone.BATTLEFIELD, playerA, "Hundred-Handed One", 1); + // For monstrosity + addCard(Zone.BATTLEFIELD, playerA, "Plains", 6); + + addCard(Zone.BATTLEFIELD, playerB, "Bronze Sable", 1); // 2/1 + addCard(Zone.BATTLEFIELD, playerB, "Fabled Hero", 1); // 2/2 double strike + + // Make hundred-handed one monstrous + activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}{W}{W}{W}: Monstrosity 3."); + + // Attack with all 4 creatures and try and block both with hundred-handed one + attack(2, playerB, "Bronze Sable"); + attack(2, playerB, "Fabled Hero"); + + block(2, playerA, "Hundred-Handed One", "Bronze Sable"); + block(2, playerA, "Hundred-Handed One", "Fabled Hero"); + + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + + // Will not fail this time as hundred-handed one is monstrous and can block up to 100 creatures + execute(); + + // Was a 3/5 but monstrosity 3 + assertPowerToughness(playerA, "Hundred-Handed One", 6, 8); + // No one has been hit + assertLife(playerA, 20); + assertLife(playerB, 20); + } + + /* + * Reported bug: Night Market Guard was able to block a creature with Menace + */ + @Test + public void testNightMarketGuardShouldNotBlockCreatureWithMenace() + { + /* + Night Market Guard {3} 3/1 + Artifact Creature — Construct + Night Market Guard can block an additional creature each combat. + */ + String nMarketGuard = "Night Market Guard"; + + /* + Embraal Bruiser {1}{B} + Creature - Human Warrior + Embraal Bruiser enters the battlefield tapped. + Embraal Bruiser has menace as long as you control an artifact. + */ + String eBruiser = "Embraal Bruiser"; + + /* + {0} 1/1 + * Artifact Creature — Construct + */ + String memnite = "Memnite"; + + addCard(Zone.BATTLEFIELD, playerA, nMarketGuard); + addCard(Zone.BATTLEFIELD, playerB, eBruiser); + addCard(Zone.BATTLEFIELD, playerB, memnite); // only here to grant Embraal Menace + + attack(4, playerB, eBruiser); + block(4, playerA, nMarketGuard, eBruiser); + + setStopAt(4, PhaseStep.POSTCOMBAT_MAIN); + + // Catch the illegal block + try { + execute(); + } catch(UnsupportedOperationException e) { + assertEquals("Embraal Bruiser is blocked by 1 creature(s). It has to be blocked by 2 or more.", e.getMessage()); + } + + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index e58a4f17cb..7bf5099618 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -28,13 +28,10 @@ package org.mage.test.player; import java.io.Serializable; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; +import java.util.*; + import mage.MageObject; +import mage.MageObjectReference; import mage.abilities.Abilities; import mage.abilities.Ability; import mage.abilities.ActivatedAbility; @@ -65,15 +62,10 @@ import mage.counters.Counter; import mage.counters.Counters; import mage.filter.Filter; import mage.filter.FilterPermanent; -import mage.filter.common.FilterAttackingCreature; -import mage.filter.common.FilterCreatureForCombat; -import mage.filter.common.FilterCreatureForCombatBlock; -import mage.filter.common.FilterCreatureOrPlayer; -import mage.filter.common.FilterPlaneswalkerPermanent; +import mage.filter.common.*; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.NamePredicate; import mage.filter.predicate.permanent.AttackingPredicate; -import mage.filter.predicate.permanent.BlockingPredicate; import mage.filter.predicate.permanent.SummoningSicknessPredicate; import mage.game.Game; import mage.game.Graveyard; @@ -181,6 +173,11 @@ public class TestPlayer implements Player { return null; } + // Gets all permanents that match the filter + protected List findPermanents(FilterPermanent filter, UUID controllerId, Game game) { + return game.getBattlefield().getAllActivePermanents(filter, controllerId, game); + } + private boolean checkExecuteCondition(String[] groups, Game game) { if (groups[2].startsWith("spellOnStack=")) { String spellOnStack = groups[2].substring(13); @@ -289,7 +286,7 @@ public class TestPlayer implements Player { int index = 0; int targetsSet = 0; for (String targetName : targetList) { - Mode selectedMode = null; + Mode selectedMode; if (targetName.startsWith("mode=")) { int modeNr = Integer.parseInt(targetName.substring(5, 6)); if (modeNr == 0 || modeNr > (ability.getModes().isEachModeMoreThanOnce() ? ability.getModes().getSelectedModes().size() : ability.getModes().size())) { @@ -561,27 +558,88 @@ public class TestPlayer implements Player { @Override public void selectBlockers(Game game, UUID defendingPlayerId) { UUID opponentId = game.getOpponents(computerPlayer.getId()).iterator().next(); + // Map of Blocker reference -> list of creatures blocked + Map> blockedCreaturesByCreature = new HashMap<>(); for (PlayerAction action : actions) { if (action.getTurnNum() == game.getTurnNum() && action.getAction().startsWith("block:")) { String command = action.getAction(); command = command.substring(command.indexOf("block:") + 6); String[] groups = command.split("\\$"); - FilterCreatureForCombatBlock filterBlocker = new FilterCreatureForCombatBlock(); - filterBlocker.add(new NamePredicate(groups[0])); - filterBlocker.add(Predicates.not(new BlockingPredicate())); - Permanent blocker = findPermanent(filterBlocker, computerPlayer.getId(), game); - if (blocker != null) { - FilterAttackingCreature filterAttacker = new FilterAttackingCreature(); - filterAttacker.add(new NamePredicate(groups[1])); - Permanent attacker = findPermanent(filterAttacker, opponentId, game); - if (attacker != null) { - computerPlayer.declareBlocker(defendingPlayerId, blocker.getId(), attacker.getId(), game); + FilterAttackingCreature filterAttacker = new FilterAttackingCreature(); + filterAttacker.add(new NamePredicate(groups[1])); + Permanent attacker = findPermanent(filterAttacker, opponentId, game); + FilterControlledPermanent filterPermanent = new FilterControlledPermanent(); + filterPermanent.add(new NamePredicate(groups[0])); + // Get all possible blockers - those with the same name on the battlefield + List possibleBlockers = findPermanents(filterPermanent, computerPlayer.getId(), game); + if (!possibleBlockers.isEmpty() && attacker != null) { + boolean blockerFound = false; + for(Permanent blocker: possibleBlockers) { + // See if it can block this creature + if(canBlockAnother(game, blocker, attacker, blockedCreaturesByCreature)) { + computerPlayer.declareBlocker(defendingPlayerId, blocker.getId(), attacker.getId(), game); + blockerFound = true; + break; + } + } + // If we haven't found a blocker then an illegal block has been made in the test + if(!blockerFound) { + throw new UnsupportedOperationException(groups[0] + " cannot block " + groups[1]); } } } + checkMultipleBlockers(game, blockedCreaturesByCreature); } } + // Checks if a creature can block at least one more creature + private boolean canBlockAnother(Game game, Permanent blocker, Permanent attacker, Map> blockedCreaturesByCreature) { + MageObjectReference blockerRef = new MageObjectReference(blocker, game); + // See if we already reference this blocker + for(MageObjectReference r: blockedCreaturesByCreature.keySet()) { + if(r.equals(blockerRef)) { + // Use the existing reference if we do + blockerRef = r; + } + } + List blocked = blockedCreaturesByCreature.getOrDefault(blockerRef, new ArrayList<>()); + int numBlocked = blocked.size(); + // Can't block any more creatures + if(++numBlocked > blocker.getMaxBlocks()) { + return false; + } + // Add the attacker reference to the list of creatures this creature is blocking + blocked.add(new MageObjectReference(attacker, game)); + blockedCreaturesByCreature.put(blockerRef, blocked); + return true; + } + + // Check for Menace type abilities - if creatures can be blocked by >X or > blockedCreaturesByCreature) { + // Stores the total number of blockers for each attacker + Map blockersForAttacker = new HashMap<>(); + // Calculate the number of blockers each attacker has + for(List attackers : blockedCreaturesByCreature.values()) { + for(MageObjectReference mr: attackers) { + Integer blockers = blockersForAttacker.getOrDefault(mr, 0); + blockersForAttacker.put(mr, blockers+1); + } + } + // Check each attacker is blocked by an allowed amount of creatures + for(Map.Entry entry: blockersForAttacker.entrySet()) { + Permanent attacker = entry.getKey().getPermanent(game); + Integer blockers = entry.getValue(); + // If getMaxBlockedBy() == 0 it means any number of creatures can block this creature + if(attacker.getMaxBlockedBy() != 0 && blockers > attacker.getMaxBlockedBy()) { + throw new UnsupportedOperationException(attacker.getName() + " is blocked by " + blockers + " creature(s). It can only be blocked by " + attacker.getMaxBlockedBy() + " or less."); + } + else if(blockers < attacker.getMinBlockedBy()) { + throw new UnsupportedOperationException(attacker.getName() + " is blocked by " + blockers + " creature(s). It has to be blocked by " + attacker.getMinBlockedBy() + " or more."); + } + } + // No errors raised - all the blockers pass the test! + } + @Override public Mode chooseMode(Modes modes, Ability source, Game game) { if (!modesSet.isEmpty() && modes.getMaxModes() > modes.getSelectedModes().size()) {