implement [VIS] Pygmy Hippo

This commit is contained in:
xenohedron 2024-06-15 12:57:50 -04:00
parent 2a6b053e17
commit f08d5acb30
6 changed files with 281 additions and 93 deletions

View file

@ -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<Permanent> ignorePermanents = new ArrayList<>();
Map<Permanent, List<ActivatedManaAbilityImpl>> 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<ActivatedManaAbilityImpl> 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<Permanent> 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<ManaPoolItem> 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<ManaPoolItem> 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;
}
}

View file

@ -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;
}
}

View file

@ -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));

View file

@ -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 isnt 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);
}
}

View file

@ -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;

View file

@ -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<Permanent> ignorePermanents = new ArrayList<>();
Map<Permanent, List<ActivatedManaAbilityImpl>> 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<ActivatedManaAbilityImpl> 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<Permanent> 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;
}
}