diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/mana/phyrexian/PhyrexianManaTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/mana/phyrexian/PhyrexianManaTest.java index 69e7f6d90e1..8ce24a2953c 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/mana/phyrexian/PhyrexianManaTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/mana/phyrexian/PhyrexianManaTest.java @@ -31,25 +31,35 @@ public class PhyrexianManaTest extends CardTestPlayerBase { @Test public void testKrrikOnlyUsableByController() { - addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); + setStrictChooseMode(true); + + // ({B/P} can be paid with either {B} or 2 life.) + // Lifelink + // For each {B} in a cost, you may pay 2 life rather than pay that mana. + // Whenever you cast a black spell, put a +1/+1 counter on K'rrik, Son of Yawgmoth. addCard(Zone.BATTLEFIELD, playerA, "K'rrik, Son of Yawgmoth"); addCard(Zone.HAND, playerA, "Banehound"); - addCard(Zone.BATTLEFIELD, playerB, "Swamp", 1); - addCard(Zone.HAND, playerB, "Banehound"); + // Lifelink, haste + addCard(Zone.HAND, playerB, "Banehound"); // Creature {B} 1/1 - setChoice(playerA, "Yes"); + checkPlayableAbility("pay 2 life for Banehound", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Banehound", true); + + setChoice(playerA, "Yes"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Banehound"); - setChoice(playerB, "Yes"); - castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Banehound"); + + checkPlayableAbility("no Mana for Banehound", 2, PhaseStep.PRECOMBAT_MAIN, playerB, "Cast Banehound", false); + setStopAt(2, PhaseStep.END_TURN); execute(); + assertAllCommandsUsed(); + //PlayerA pays life but PlayerB cannot assertLife(playerA, 18); assertLife(playerB, 20); assertPermanentCount(playerA, "Banehound", 1); - assertPermanentCount(playerB, "Banehound", 1); + assertPermanentCount(playerB, "Banehound", 0); } @Test @@ -121,4 +131,65 @@ public class PhyrexianManaTest extends CardTestPlayerBase { assertGraveyardCount(playerA, "Dismember", 1); assertGraveyardCount(playerB, "Banehound", 1); } + + @Test + public void testPlayerCanCastBanehoundWithoutAvailableBlackMana() { + setStrictChooseMode(true); + + // ({B/P} can be paid with either {B} or 2 life.) + // Lifelink + // For each {B} in a cost, you may pay 2 life rather than pay that mana. + // Whenever you cast a black spell, put a +1/+1 counter on K'rrik, Son of Yawgmoth. + addCard(Zone.BATTLEFIELD, playerA, "K'rrik, Son of Yawgmoth"); // Creature {4}{B/P}{B/P}{B/P} 2/2 + addCard(Zone.HAND, playerA, "Banehound"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Banehound"); + setChoice(playerA, "Yes"); // Pay 2 life for {B} + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertAllCommandsUsed(); + + //PlayerA pays life + assertLife(playerA, 18); + assertLife(playerB, 20); + + + assertPermanentCount(playerA, "Banehound", 1); + } + + @Test + public void testPlayerEffectNotUsableIfKrrikNotOnBattlefield() { + setStrictChooseMode(true); + + // addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); + // ({B/P} can be paid with either {B} or 2 life.) + // Lifelink + // For each {B} in a cost, you may pay 2 life rather than pay that mana. + // Whenever you cast a black spell, put a +1/+1 counter on K'rrik, Son of Yawgmoth. + addCard(Zone.BATTLEFIELD, playerA, "K'rrik, Son of Yawgmoth"); // Creature {4}{B/P}{B/P}{B/P} 2/2 + addCard(Zone.HAND, playerA, "Banehound"); + + addCard(Zone.HAND, playerB, "Lightning Bolt", 1); + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 1); + + castSpell(1, PhaseStep.UPKEEP, playerB, "Lightning Bolt", "K'rrik, Son of Yawgmoth"); + + checkPlayableAbility("no Mana for Banehound", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Banehound", false); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertAllCommandsUsed(); + + assertGraveyardCount(playerB, "Lightning Bolt", 1); + assertGraveyardCount(playerA, "K'rrik, Son of Yawgmoth", 1); + + assertHandCount(playerA, "Banehound", 1); + //PlayerA pays life + assertLife(playerA, 20); + assertLife(playerB, 20); + + } } 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 fd09f3522e4..f689caef30e 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 @@ -4008,11 +4008,6 @@ public class TestPlayer implements Player { computerPlayer.addPhyrexianToColors(colors); } - @Override - public void removePhyrexianFromColors(FilterMana colors) { - computerPlayer.removePhyrexianFromColors(colors); - } - @Override public FilterMana getPhyrexianColors() { return computerPlayer.getPhyrexianColors(); diff --git a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java index 239a240ee81..0a4dad592d7 100644 --- a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java +++ b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java @@ -1408,11 +1408,6 @@ public class PlayerStub implements Player { } - @Override - public void removePhyrexianFromColors(FilterMana colors) { - - } - @Override public FilterMana getPhyrexianColors() { return (new FilterMana()); diff --git a/Mage/src/main/java/mage/abilities/costs/mana/ManaCostsImpl.java b/Mage/src/main/java/mage/abilities/costs/mana/ManaCostsImpl.java index c92df834ddf..7618ad20251 100644 --- a/Mage/src/main/java/mage/abilities/costs/mana/ManaCostsImpl.java +++ b/Mage/src/main/java/mage/abilities/costs/mana/ManaCostsImpl.java @@ -120,7 +120,7 @@ public class ManaCostsImpl extends ArrayList implements M } Player player = game.getPlayer(controllerId); - handleKrrikPhyrexianManaCosts(controllerId, ability, game); + handleLikePhyrexianManaCosts(controllerId, ability, game); // e.g. K'rrik, Son of Yawgmoth if (!player.getManaPool().isForcedToPay()) { assignPayment(game, ability, player.getManaPool(), this); } @@ -170,11 +170,6 @@ public class ManaCostsImpl extends ArrayList implements M while (manaCostIterator.hasNext()) { ManaCost manaCost = manaCostIterator.next(); - PhyrexianManaCost tempPhyrexianCost = null; - Mana mana = manaCost.getMana(); - - FilterMana phyrexianColors = player.getPhyrexianColors(); - if (manaCost instanceof PhyrexianManaCost) { PhyrexianManaCost phyrexianManaCost = (PhyrexianManaCost) manaCost; PayLifeCost payLifeCost = new PayLifeCost(2); @@ -189,7 +184,7 @@ public class ManaCostsImpl extends ArrayList implements M tempCosts.pay(source, game, source.getSourceId(), player.getId(), false, null); } - private void handleKrrikPhyrexianManaCosts(UUID payingPlayerId, Ability source, Game game) { + private void handleLikePhyrexianManaCosts(UUID payingPlayerId, Ability source, Game game) { Player player = game.getPlayer(payingPlayerId); if (this == null || player == null) { return; // nothing to be done without any mana costs. prevents NRE from occurring here diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java index df36f3469cb..90405edeee4 100644 --- a/Mage/src/main/java/mage/players/Player.java +++ b/Mage/src/main/java/mage/players/Player.java @@ -932,10 +932,16 @@ public interface Player extends MageItem, Copyable { List getDesignations(); + /** + * Set the mana colors the user can pay with 2 life instead + * @param colors + */ void addPhyrexianToColors(FilterMana colors); - void removePhyrexianFromColors(FilterMana colors); - + /** + * Mana colors the player can pay instead with 2 life + * @return + */ FilterMana getPhyrexianColors(); } diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 8d388fddf27..fe2d86a7eca 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -174,6 +174,7 @@ public abstract class PlayerImpl implements Player, Serializable { protected List designations = new ArrayList<>(); + // mana colors the player can handle like Phyrexian mana protected FilterMana phyrexianColors; // Used during available mana calculation to give back possible available net mana from triggered mana abilities (No need to copy) @@ -196,7 +197,7 @@ public abstract class PlayerImpl implements Player, Serializable { manaPool = new ManaPool(playerId); library = new Library(playerId); sideboard = new CardsImpl(); - phyrexianColors = new FilterMana(); + phyrexianColors = null; } protected PlayerImpl(UUID id) { @@ -279,8 +280,7 @@ public abstract class PlayerImpl implements Player, Serializable { this.castSourceIdCosts.putAll(player.castSourceIdCosts); this.payManaMode = player.payManaMode; - this.phyrexianColors = player.phyrexianColors.copy(); - + this.phyrexianColors = player.getPhyrexianColors() != null ? player.phyrexianColors.copy() : null; this.designations.addAll(player.designations); } @@ -357,8 +357,8 @@ public abstract class PlayerImpl implements Player, Serializable { for (Entry> entry : player.getCastSourceIdCosts().entrySet()) { this.castSourceIdCosts.put(entry.getKey(), entry.getValue().copy()); } - this.phyrexianColors = player.getPhyrexianColors().copy(); + this.phyrexianColors = player.getPhyrexianColors() != null ? player.getPhyrexianColors().copy() : null; this.designations.clear(); this.designations.addAll(player.getDesignations()); @@ -434,7 +434,7 @@ public abstract class PlayerImpl implements Player, Serializable { this.clearCastSourceIdManaCosts(); this.getManaPool().init(); // needed to remove mana that not empties on step change from previous game if left - this.phyrexianColors = new FilterMana(); + this.phyrexianColors = null; this.designations.clear(); } @@ -459,7 +459,7 @@ public abstract class PlayerImpl implements Player, Serializable { this.alternativeSourceCosts.clear(); this.clearCastSourceIdManaCosts(); this.getManaPool().clearEmptyManaPoolRules(); - this.phyrexianColors = new FilterMana(); + this.phyrexianColors = null; } @Override @@ -3115,6 +3115,11 @@ public abstract class PlayerImpl implements Player, Serializable { if (availableMana == null) { return true; } + // Check for pay option with like phyrexian mana + if (getPhyrexianColors() != null) { + addPhyrexianLikePayOptions(abilityOptions, availableMana, game); + } + MageObjectReference permittingObject = game.getContinuousEffects().asThough(ability.getSourceId(), AsThoughEffectType.SPEND_OTHER_MANA, ability, ability.getControllerId(), game); for (Mana mana : abilityOptions) { @@ -3137,6 +3142,49 @@ public abstract class PlayerImpl implements Player, Serializable { return false; } + private void addPhyrexianLikePayOptions(ManaOptions abilityOptions, ManaOptions availableMana, Game game) { + int maxLifeMana = getLife() / 2; + if (maxLifeMana > 0) { + Set phyrexianOptions = new HashSet<>(); + for (Mana mana : abilityOptions) { + int availableLifeMana = maxLifeMana; + if (getPhyrexianColors().isBlack()) { + createReducedManaPayOption(availableLifeMana, mana, phyrexianOptions, ManaType.BLACK); + } + if (getPhyrexianColors().isBlue()) { + createReducedManaPayOption(availableLifeMana, mana, phyrexianOptions, ManaType.BLUE); + } + if (getPhyrexianColors().isRed()) { + createReducedManaPayOption(availableLifeMana, mana, phyrexianOptions, ManaType.RED); + } + if (getPhyrexianColors().isGreen()) { + createReducedManaPayOption(availableLifeMana, mana, phyrexianOptions, ManaType.GREEN); + } + if (getPhyrexianColors().isWhite()) { + createReducedManaPayOption(availableLifeMana, mana, phyrexianOptions, ManaType.WHITE); + } + } + abilityOptions.addAll(phyrexianOptions); + } + } + + private int createReducedManaPayOption(int availableLifeMana, Mana oldPayOption, Set phyrexianOptions, ManaType manaType) { + if (oldPayOption.get(manaType) > 0) { + Mana manaCopy = oldPayOption.copy(); + int restVal; + if (availableLifeMana > oldPayOption.get(manaType)) { + restVal = 0; + availableLifeMana -= oldPayOption.get(manaType); + } else { + restVal = oldPayOption.get(manaType) - availableLifeMana; + availableLifeMana = 0; + } + manaCopy.set(manaType, restVal); + phyrexianOptions.add(manaCopy); + } + return availableLifeMana; + } + protected boolean canPlayCardByAlternateCost(Card sourceObject, ManaOptions availableMana, Ability ability, Game game) { if (sourceObject != null && !(sourceObject instanceof Permanent)) { Ability copyAbility; // for alternative cost and reduce tries @@ -4544,39 +4592,24 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public void addPhyrexianToColors(FilterMana colors) { - if (colors.isWhite()) { - this.phyrexianColors.setWhite(true); - } - if (colors.isBlue()) { - this.phyrexianColors.setBlue(true); - } - if (colors.isBlack()) { - this.phyrexianColors.setBlack(true); - } - if (colors.isRed()) { - this.phyrexianColors.setRed(true); - } - if (colors.isGreen()) { - this.phyrexianColors.setGreen(true); - } - } - - @Override - public void removePhyrexianFromColors(FilterMana colors) { - if (colors.isWhite()) { - this.phyrexianColors.setWhite(false); - } - if (colors.isBlue()) { - this.phyrexianColors.setBlue(false); - } - if (colors.isBlack()) { - this.phyrexianColors.setBlack(false); - } - if (colors.isRed()) { - this.phyrexianColors.setRed(false); - } - if (colors.isGreen()) { - this.phyrexianColors.setGreen(false); + if (phyrexianColors == null) { + phyrexianColors = colors.copy(); + } else { + if (colors.isWhite()) { + this.phyrexianColors.setWhite(true); + } + if (colors.isBlue()) { + this.phyrexianColors.setBlue(true); + } + if (colors.isBlack()) { + this.phyrexianColors.setBlack(true); + } + if (colors.isRed()) { + this.phyrexianColors.setRed(true); + } + if (colors.isGreen()) { + this.phyrexianColors.setGreen(true); + } } } diff --git a/Mage/src/main/java/mage/players/StubPlayer.java b/Mage/src/main/java/mage/players/StubPlayer.java index c9d959e34a5..2f926a12e4b 100644 --- a/Mage/src/main/java/mage/players/StubPlayer.java +++ b/Mage/src/main/java/mage/players/StubPlayer.java @@ -225,11 +225,6 @@ public class StubPlayer extends PlayerImpl implements Player { } - @Override - public void removePhyrexianFromColors(FilterMana colors) { - - } - @Override public FilterMana getPhyrexianColors() { return (new FilterMana());