diff --git a/Mage.Sets/src/mage/cards/d/DrainPower.java b/Mage.Sets/src/mage/cards/d/DrainPower.java index 6a5b4ae07ac..403c22e8603 100644 --- a/Mage.Sets/src/mage/cards/d/DrainPower.java +++ b/Mage.Sets/src/mage/cards/d/DrainPower.java @@ -1,25 +1,20 @@ package mage.cards.d; import mage.abilities.Ability; -import mage.abilities.costs.mana.ManaCost; import mage.abilities.effects.OneShotEffect; -import mage.abilities.mana.ActivatedManaAbilityImpl; +import mage.abilities.effects.common.TargetPlayerActivatesAllManaAbilitiesEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AbilityType; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; -import mage.filter.common.FilterLandPermanent; -import mage.filter.predicate.permanent.PermanentReferenceInCollectionPredicate; import mage.game.Game; -import mage.game.permanent.Permanent; import mage.players.ManaPoolItem; import mage.players.Player; -import mage.target.TargetPermanent; import mage.target.TargetPlayer; -import java.util.*; +import java.util.List; +import java.util.UUID; /** * @author L_J @@ -46,9 +41,7 @@ public final class DrainPower extends CardImpl { class DrainPowerEffect extends OneShotEffect { - private static final FilterLandPermanent filter = new FilterLandPermanent(); - - public DrainPowerEffect() { + DrainPowerEffect() { super(Outcome.PutManaInPool); this.staticText = "Target player activates a mana ability of each land they control. Then that player loses all unspent mana and you add the mana lost this way"; } @@ -64,89 +57,27 @@ class DrainPowerEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); Player targetPlayer = game.getPlayer(source.getFirstTarget()); - if (targetPlayer != null) { - List ignorePermanents = new ArrayList<>(); - Map> manaAbilitiesMap = new HashMap<>(); - TargetPermanent target = null; + if (targetPlayer == null) { + return false; + } + new TargetPlayerActivatesAllManaAbilitiesEffect().setTargetPointer(this.getTargetPointer().copy()) + .apply(game, source); - while (true) { - targetPlayer.setPayManaMode(true); - manaAbilitiesMap.clear(); - for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, targetPlayer.getId(), game)) { - if (!ignorePermanents.contains(permanent)) { - List manaAbilities = new ArrayList<>(); - abilitySearch: - for (Ability ability : permanent.getAbilities()) { - if (AbilityType.ACTIVATED_MANA.equals(ability.getAbilityType())) { - ActivatedManaAbilityImpl manaAbility = (ActivatedManaAbilityImpl) ability; - if (manaAbility.canActivate(targetPlayer.getId(), game).canActivate()) { - // canActivate can't check for mana abilities that require a mana cost, if the payment isn't possible (Cabal Coffers etc) - // so it's necessary to filter them out manually - might be buggy in some fringe cases - for (ManaCost manaCost : manaAbility.getManaCosts()) { - if (!targetPlayer.getManaPool().getMana().includesMana(manaCost.getMana())) { - continue abilitySearch; - } - } - manaAbilities.add(manaAbility); - } - } - } - if (!manaAbilities.isEmpty()) { - manaAbilitiesMap.put(permanent, manaAbilities); - } - } - } - if (manaAbilitiesMap.isEmpty()) { - break; - } - - List permList = new ArrayList<>(manaAbilitiesMap.keySet()); - Permanent permanent; - if (permList.size() > 1 || target != null) { - FilterLandPermanent filter2 = new FilterLandPermanent("land you control to tap for mana (remaining: " + permList.size() + ')'); - filter2.add(new PermanentReferenceInCollectionPredicate(permList, game)); - target = new TargetPermanent(1, 1, filter2, true); - while (!target.isChosen(game) && target.canChoose(targetPlayer.getId(), source, game) && targetPlayer.canRespond()) { - targetPlayer.chooseTarget(Outcome.Neutral, target, source, game); - } - permanent = game.getPermanent(target.getFirstTarget()); - } else { - permanent = permList.get(0); - } - if (permanent != null) { - int i = 0; - for (ActivatedManaAbilityImpl manaAbility : manaAbilitiesMap.get(permanent)) { - i++; - if (manaAbilitiesMap.get(permanent).size() <= i - || targetPlayer.chooseUse(Outcome.Neutral, "Activate mana ability \"" + manaAbility.getRule() + "\" of " + permanent.getLogName() - + "? (Choose \"no\" to activate next mana ability)", source, game)) { - boolean originalCanUndo = manaAbility.isUndoPossible(); - manaAbility.setUndoPossible(false); // prevents being able to undo Drain Power - if (targetPlayer.activateAbility(manaAbility, game)) { - ignorePermanents.add(permanent); - } - manaAbility.setUndoPossible(originalCanUndo); // resets undoPossible to its original state - break; - } - } - } - } - targetPlayer.setPayManaMode(false); - - // 106.12. One card (Drain Power) causes one player to lose unspent mana and another to add “the mana lost this way.” (Note that these may be the same player.) - // This empties the former player's mana pool and causes the mana emptied this way to be put into the latter player's mana pool. Which permanents, spells, and/or - // abilities produced that mana are unchanged, as are any restrictions or additional effects associated with any of that mana. - List manaItems = targetPlayer.getManaPool().getManaItems(); - targetPlayer.getManaPool().emptyPool(game); - for (ManaPoolItem manaPoolItem : manaItems) { - controller.getManaPool().addMana( - manaPoolItem.isConditional() ? manaPoolItem.getConditionalMana() : manaPoolItem.getMana(), - game, source, Duration.EndOfTurn.equals(manaPoolItem.getDuration())); - } + // 106.12. One card (Drain Power) causes one player to lose unspent mana and another to add “the mana lost this way.” (Note that these may be the same player.) + // This empties the former player's mana pool and causes the mana emptied this way to be put into the latter player's mana pool. Which permanents, spells, and/or + // abilities produced that mana are unchanged, as are any restrictions or additional effects associated with any of that mana. + List manaItems = targetPlayer.getManaPool().getManaItems(); + targetPlayer.getManaPool().emptyPool(game); + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { return true; } - return false; + for (ManaPoolItem manaPoolItem : manaItems) { + controller.getManaPool().addMana( + manaPoolItem.isConditional() ? manaPoolItem.getConditionalMana() : manaPoolItem.getMana(), + game, source, Duration.EndOfTurn.equals(manaPoolItem.getDuration())); + } + return true; } } diff --git a/Mage.Sets/src/mage/cards/p/PygmyHippo.java b/Mage.Sets/src/mage/cards/p/PygmyHippo.java new file mode 100644 index 00000000000..88a9a2784d3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PygmyHippo.java @@ -0,0 +1,88 @@ +package mage.cards.p; + +import mage.MageInt; +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.common.AttacksAndIsNotBlockedTriggeredAbility; +import mage.abilities.common.delayed.AtTheBeginOfMainPhaseDelayedTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.TargetPlayerActivatesAllManaAbilitiesEffect; +import mage.abilities.effects.common.continuous.AssignNoCombatDamageSourceEffect; +import mage.abilities.effects.mana.AddManaToManaPoolSourceControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.players.Player; + +import java.util.UUID; + +/** + * @author xenohedron + */ +public final class PygmyHippo extends CardImpl { + + public PygmyHippo(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{U}"); + + this.subtype.add(SubType.HIPPO); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Whenever Pygmy Hippo attacks and isn't blocked, you may have defending player activate a mana ability of each land they control and lose all unspent mana. If you do, Pygmy Hippo assigns no combat damage this turn and at the beginning of your next main phase this turn, you add an amount of {C} equal to the amount of mana that player lost this way. + this.addAbility(new AttacksAndIsNotBlockedTriggeredAbility(new PygmyHippoEffect(), true, true)); + } + + private PygmyHippo(final PygmyHippo card) { + super(card); + } + + @Override + public PygmyHippo copy() { + return new PygmyHippo(this); + } +} + +class PygmyHippoEffect extends OneShotEffect { + + PygmyHippoEffect() { + super(Outcome.PutManaInPool); + this.staticText = "you may have defending player activate a mana ability of each land they control" + + " and lose all unspent mana. If you do, {this} assigns no combat damage this turn" + + " and at the beginning of your next main phase this turn, you add an amount of {C} equal to" + + " the amount of mana that player lost this way"; + } + + private PygmyHippoEffect(final PygmyHippoEffect effect) { + super(effect); + } + + @Override + public PygmyHippoEffect copy() { + return new PygmyHippoEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); + if (targetPlayer == null) { + return false; + } + new TargetPlayerActivatesAllManaAbilitiesEffect().setTargetPointer(this.getTargetPointer().copy()) + .apply(game, source); + int amountToAdd = targetPlayer.getManaPool().count(); + targetPlayer.getManaPool().emptyPool(game); + + game.addEffect(new AssignNoCombatDamageSourceEffect(Duration.EndOfTurn, true), source); + + if (amountToAdd > 0) { + DelayedTriggeredAbility delayed = new AtTheBeginOfMainPhaseDelayedTriggeredAbility( + new AddManaToManaPoolSourceControllerEffect(new Mana(ManaType.COLORLESS, amountToAdd)), + false, TargetController.YOU, + AtTheBeginOfMainPhaseDelayedTriggeredAbility.PhaseSelection.NEXT_MAIN_THIS_TURN); + game.addDelayedTriggeredAbility(delayed, source); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/Visions.java b/Mage.Sets/src/mage/sets/Visions.java index 0e829559bc7..45a813d1028 100644 --- a/Mage.Sets/src/mage/sets/Visions.java +++ b/Mage.Sets/src/mage/sets/Visions.java @@ -126,6 +126,7 @@ public final class Visions extends ExpansionSet { cards.add(new SetCardInfo("Phyrexian Walker", 152, Rarity.COMMON, mage.cards.p.PhyrexianWalker.class)); cards.add(new SetCardInfo("Pillar Tombs of Aku", 67, Rarity.RARE, mage.cards.p.PillarTombsOfAku.class)); cards.add(new SetCardInfo("Prosperity", 40, Rarity.UNCOMMON, mage.cards.p.Prosperity.class)); + cards.add(new SetCardInfo("Pygmy Hippo", 133, Rarity.RARE, mage.cards.p.PygmyHippo.class)); cards.add(new SetCardInfo("Python", 68, Rarity.COMMON, mage.cards.p.Python.class)); cards.add(new SetCardInfo("Quicksand", 166, Rarity.UNCOMMON, mage.cards.q.Quicksand.class)); cards.add(new SetCardInfo("Quirion Druid", 116, Rarity.RARE, mage.cards.q.QuirionDruid.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/vis/PygmyHippoTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/vis/PygmyHippoTest.java new file mode 100644 index 00000000000..c0c7e994e14 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/vis/PygmyHippoTest.java @@ -0,0 +1,49 @@ +package org.mage.test.cards.single.vis; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author xenohedron + */ +public class PygmyHippoTest extends CardTestPlayerBase { + + /* + * Whenever Pygmy Hippo attacks and isn’t blocked, you may have defending player activate a mana ability of + * each land they control and lose all unspent mana. If you do, Pygmy Hippo assigns no combat damage this turn + * and at the beginning of your next main phase this turn, you add an amount of {C} equal to the amount of mana + * that player lost this way. + */ + private static final String hippo = "Pygmy Hippo"; // {G}{U} 2/2 + private static final String sentinel = "Gilded Sentinel"; // {4} 3/3 + + @Test + public void testPygmyHippo() { + + addCard(Zone.BATTLEFIELD, playerA, hippo); + addCard(Zone.HAND, playerA, sentinel); + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 4); + + attack(1, playerA, hippo, playerB); + setChoice(playerA, true); // yes to ability + setChoice(playerB, "Swamp"); // which land to tap + setChoice(playerB, "Swamp"); // which land to tap + setChoice(playerB, "Swamp"); // which land to tap + setChoice(playerB, "Swamp"); // which land to tap + + waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, sentinel); // with four gained mana + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 20); + assertLife(playerB, 20); // no combat damage assigned + assertPowerToughness(playerA, hippo, 2, 2); + assertPowerToughness(playerA, sentinel, 3, 3); + assertTappedCount("Swamp", true, 4); + } +} diff --git a/Mage/src/main/java/mage/abilities/common/delayed/AtTheBeginOfMainPhaseDelayedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/delayed/AtTheBeginOfMainPhaseDelayedTriggeredAbility.java index adaf8d8f632..6cb1fed1c0e 100644 --- a/Mage/src/main/java/mage/abilities/common/delayed/AtTheBeginOfMainPhaseDelayedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/delayed/AtTheBeginOfMainPhaseDelayedTriggeredAbility.java @@ -18,7 +18,7 @@ public class AtTheBeginOfMainPhaseDelayedTriggeredAbility extends DelayedTrigger public enum PhaseSelection { NEXT_PRECOMBAT_MAIN("next precombat main phase"), - NEXT_POSTCOMAT_MAIN("next postcombat main phase"), + NEXT_POSTCOMBAT_MAIN("next postcombat main phase"), NEXT_MAIN("next main phase"), NEXT_MAIN_THIS_TURN("next main phase this turn", Duration.EndOfTurn); @@ -92,7 +92,7 @@ public class AtTheBeginOfMainPhaseDelayedTriggeredAbility extends DelayedTrigger case NEXT_MAIN: case NEXT_MAIN_THIS_TURN: return EventType.PRECOMBAT_MAIN_PHASE_PRE == eventType || EventType.POSTCOMBAT_MAIN_PHASE_PRE == eventType; - case NEXT_POSTCOMAT_MAIN: + case NEXT_POSTCOMBAT_MAIN: return EventType.POSTCOMBAT_MAIN_PHASE_PRE == eventType; case NEXT_PRECOMBAT_MAIN: return EventType.PRECOMBAT_MAIN_PHASE_PRE == eventType; diff --git a/Mage/src/main/java/mage/abilities/effects/common/TargetPlayerActivatesAllManaAbilitiesEffect.java b/Mage/src/main/java/mage/abilities/effects/common/TargetPlayerActivatesAllManaAbilitiesEffect.java new file mode 100644 index 00000000000..ea01fc5a1ca --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/TargetPlayerActivatesAllManaAbilitiesEffect.java @@ -0,0 +1,119 @@ +package mage.abilities.effects.common; + +import mage.abilities.Ability; +import mage.abilities.costs.mana.ManaCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.mana.ActivatedManaAbilityImpl; +import mage.constants.AbilityType; +import mage.constants.Outcome; +import mage.filter.StaticFilters; +import mage.filter.common.FilterLandPermanent; +import mage.filter.predicate.permanent.PermanentReferenceInCollectionPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author L_J + */ +public class TargetPlayerActivatesAllManaAbilitiesEffect extends OneShotEffect { + + /** + * For use in Drain Power and Pygmy Hippo + */ + public TargetPlayerActivatesAllManaAbilitiesEffect() { + super(Outcome.Detriment); + } + + protected TargetPlayerActivatesAllManaAbilitiesEffect(final TargetPlayerActivatesAllManaAbilitiesEffect effect) { + super(effect); + } + + @Override + public TargetPlayerActivatesAllManaAbilitiesEffect copy() { + return new TargetPlayerActivatesAllManaAbilitiesEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); + if (targetPlayer == null) { + return false; + } + List ignorePermanents = new ArrayList<>(); + Map> manaAbilitiesMap = new HashMap<>(); + TargetPermanent target = null; + + while (targetPlayer.canRespond()) { + targetPlayer.setPayManaMode(true); + manaAbilitiesMap.clear(); + for (Permanent permanent : game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_LAND, targetPlayer.getId(), game)) { + if (!ignorePermanents.contains(permanent)) { + List manaAbilities = new ArrayList<>(); + abilitySearch: + for (Ability ability : permanent.getAbilities()) { + if (AbilityType.ACTIVATED_MANA.equals(ability.getAbilityType())) { + ActivatedManaAbilityImpl manaAbility = (ActivatedManaAbilityImpl) ability; + if (manaAbility.canActivate(targetPlayer.getId(), game).canActivate()) { + // canActivate can't check for mana abilities that require a mana cost, if the payment isn't possible (Cabal Coffers etc) + // so it's necessary to filter them out manually - might be buggy in some fringe cases + for (ManaCost manaCost : manaAbility.getManaCosts()) { + if (!targetPlayer.getManaPool().getMana().includesMana(manaCost.getMana())) { + continue abilitySearch; + } + } + manaAbilities.add(manaAbility); + } + } + } + if (!manaAbilities.isEmpty()) { + manaAbilitiesMap.put(permanent, manaAbilities); + } + } + } + if (manaAbilitiesMap.isEmpty()) { + break; + } + + List permList = new ArrayList<>(manaAbilitiesMap.keySet()); + Permanent permanent; + if (permList.size() > 1 || target != null) { + FilterLandPermanent filter2 = new FilterLandPermanent("land you control to tap for mana (remaining: " + permList.size() + ')'); + filter2.add(new PermanentReferenceInCollectionPredicate(permList, game)); + target = new TargetPermanent(1, 1, filter2, true); + while (!target.isChosen(game) && target.canChoose(targetPlayer.getId(), source, game) && targetPlayer.canRespond()) { + targetPlayer.choose(Outcome.Neutral, target, source, game); + } + permanent = game.getPermanent(target.getFirstTarget()); + } else { + permanent = permList.get(0); + } + if (permanent != null) { + int i = 0; + for (ActivatedManaAbilityImpl manaAbility : manaAbilitiesMap.get(permanent)) { + i++; + if (manaAbilitiesMap.get(permanent).size() <= i + || targetPlayer.chooseUse(Outcome.Neutral, "Activate mana ability \"" + manaAbility.getRule() + "\" of " + permanent.getLogName() + + "? (Choose \"no\" to activate next mana ability)", source, game)) { + boolean originalCanUndo = manaAbility.isUndoPossible(); + manaAbility.setUndoPossible(false); // prevents being able to undo Drain Power + if (targetPlayer.activateAbility(manaAbility, game)) { + ignorePermanents.add(permanent); + } + manaAbility.setUndoPossible(originalCanUndo); // resets undoPossible to its original state + break; + } + } + } + } + targetPlayer.setPayManaMode(false); + return true; + } + +}