diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ConvokeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ConvokeTest.java index b96e9efabc7..d492972341f 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ConvokeTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ConvokeTest.java @@ -1,145 +1,298 @@ - - package org.mage.test.cards.abilities.keywords; import mage.constants.PhaseStep; import mage.constants.Zone; -import mage.filter.common.FilterLandPermanent; -import mage.game.permanent.Permanent; -import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; -import org.mage.test.serverside.base.CardTestPlayerBase; +import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps; /** - * - * @author LevelX2 + * @author JayDi85 */ -public class ConvokeTest extends CardTestPlayerBase { - - /* - Test are set to Ignore because the new way to handle this alternate mana payment methods - are not supported yet from AI and getPlayable logic. - */ +public class ConvokeTest extends CardTestPlayerBaseWithAIHelps { @Test - @Ignore - public void testConvokeTwoCreatures() { - /** - * Ephemeral Shields {1}{W} - * Instant - * Convoke (Your creatures can help cast this spell. Each creature you tap while casting this spell pays for or one mana of that creature's color.) - * Target creature gains indestructible until end of turn. (Damage and effects that say "destroy" don't destroy it.) - */ + public void test_Playable_NoMana_NoConvoke() { + // {2}{R}{R} + // Convoke (Your creatures can help cast this spell. Each creature you tap while casting this spell pays for {1} or one mana of that creature’s color.) + // Stoke the Flames deals 4 damage to any target. + addCard(Zone.HAND, playerA, "Stoke the Flames", 1); - addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); // must be added because getPlayable does not take Convoke into account - + checkPlayableAbility("all", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Stoke the Flames", false); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_Playable_Mana_NoConvoke() { + // {2}{R}{R} + // Convoke (Your creatures can help cast this spell. Each creature you tap while casting this spell pays for {1} or one mana of that creature’s color.) + // Stoke the Flames deals 4 damage to any target. + addCard(Zone.HAND, playerA, "Stoke the Flames", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); + + checkPlayableAbility("all", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Stoke the Flames", true); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_Playable_NoMana_Convoke() { + // {2}{R}{R} + // Convoke (Your creatures can help cast this spell. Each creature you tap while casting this spell pays for {1} or one mana of that creature’s color.) + // Stoke the Flames deals 4 damage to any target. + addCard(Zone.HAND, playerA, "Stoke the Flames", 1); + addCard(Zone.BATTLEFIELD, playerA, "Goblin Racketeer", 4); // convoke pay + + checkPlayableAbility("all", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Stoke the Flames", true); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_Playable_Mana_Convoke() { + // {2}{R}{R} + // Convoke (Your creatures can help cast this spell. Each creature you tap while casting this spell pays for {1} or one mana of that creature’s color.) + // Stoke the Flames deals 4 damage to any target. + addCard(Zone.HAND, playerA, "Stoke the Flames", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.BATTLEFIELD, playerA, "Goblin Racketeer", 2); // convoke pay + + checkPlayableAbility("all", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Stoke the Flames", true); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_Playable_ManaPartly_ConvokePartly() { + // {2}{R}{R} + // Convoke (Your creatures can help cast this spell. Each creature you tap while casting this spell pays for {1} or one mana of that creature’s color.) + // Stoke the Flames deals 4 damage to any target. + addCard(Zone.HAND, playerA, "Stoke the Flames", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2 - 1); + addCard(Zone.BATTLEFIELD, playerA, "Goblin Racketeer", 2 - 1); // convoke pay + + checkPlayableAbility("all", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Stoke the Flames", false); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_PlayConvoke_Manual() { + // {2}{R}{R} + // Convoke (Your creatures can help cast this spell. Each creature you tap while casting this spell pays for {1} or one mana of that creature’s color.) + // Stoke the Flames deals 4 damage to any target. + addCard(Zone.HAND, playerA, "Stoke the Flames", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.BATTLEFIELD, playerA, "Goblin Racketeer", 2); // convoke pay + + // use special action to pay (need disabled auto-payment and prepared mana pool) + disableManaAutoPayment(playerA); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}", 2); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Stoke the Flames", playerB); + setChoice(playerA, "Red"); // pay 1 + setChoice(playerA, "Red"); // pay 2 + setChoice(playerA, "Convoke"); + addTarget(playerA, "Goblin Racketeer"); // pay 3 as convoke + setChoice(playerA, "Convoke"); + addTarget(playerA, "Goblin Racketeer"); // pay 4 as convoke + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertLife(playerB, 20 - 4); + } + + @Test + public void test_PlayConvoke_AI_AutoPay() { + // {2}{R}{R} + // Convoke (Your creatures can help cast this spell. Each creature you tap while casting this spell pays for {1} or one mana of that creature’s color.) + // Stoke the Flames deals 4 damage to any target. + addCard(Zone.HAND, playerA, "Stoke the Flames", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.BATTLEFIELD, playerA, "Goblin Racketeer", 2); // convoke pay + + // AI must use special actions to pay as convoke + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Stoke the Flames", playerB); + + //setStrictChooseMode(true); AI must choose targets + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertLife(playerB, 20 - 4); + } + + @Test + public void test_PlayConvoke_AI_AutoPayAsConvoke() { + // {2}{R}{R} + // Convoke (Your creatures can help cast this spell. Each creature you tap while casting this spell pays for {1} or one mana of that creature’s color.) + // Stoke the Flames deals 4 damage to any target. + addCard(Zone.HAND, playerA, "Stoke the Flames", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.BATTLEFIELD, playerA, "Goblin Racketeer", 2); // convoke pay + + // AI must use special actions to pay as convoke + // Current version uses special mana pay as last, after no normal mana available (it can be changed in the future, see playManaHandling) + // e.g. it must tap lands 2 times and convoke 2 times + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Stoke the Flames", playerB); + addTarget(playerA, "Goblin Racketeer"); // pay 1 as convoke + addTarget(playerA, "Goblin Racketeer"); // pay 2 as convoke + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertLife(playerB, 20 - 4); + } + + @Test + public void test_PlayConvoke_AI_FullPlay() { + // {2}{R}{R} + // Convoke (Your creatures can help cast this spell. Each creature you tap while casting this spell pays for {1} or one mana of that creature’s color.) + // Stoke the Flames deals 4 damage to any target. + addCard(Zone.HAND, playerA, "Stoke the Flames", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.BATTLEFIELD, playerA, "Goblin Racketeer", 2); // convoke pay + + // AI must use special actions to pay as convoke and play card + aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertLife(playerB, 20 - 4); + } + + @Test + public void test_Other_ConvokeTwoCreatures() { + // {1}{W} + // Convoke (Your creatures can help cast this spell. Each creature you tap while casting this spell pays for or one mana of that creature's color.) + // Target creature gains indestructible until end of turn. (Damage and effects that say "destroy" don't destroy it.) + addCard(Zone.HAND, playerA, "Ephemeral Shields"); addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); addCard(Zone.BATTLEFIELD, playerA, "Oreskos Swiftclaw", 1); - addCard(Zone.HAND, playerA, "Ephemeral Shields"); - - addCard(Zone.BATTLEFIELD, playerB, "Mountain", 1); - addCard(Zone.HAND, playerB, "Lightning Bolt"); - - + // AI automaticly use convoke to pay castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ephemeral Shields", "Silvercoat Lion"); - setChoice(playerA, "Yes"); - addTarget(playerA, "Silvercoat Lion^Oreskos Swiftclaw"); + addTarget(playerA, "Silvercoat Lion"); // pay 1 as convoke + addTarget(playerA, "Oreskos Swiftclaw"); // pay 2 as convoke - castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Lightning Bolt", "Silvercoat Lion"); + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); + assertAllCommandsUsed(); assertLife(playerA, 20); assertLife(playerB, 20); - assertGraveyardCount(playerB, "Lightning Bolt", 1); - assertGraveyardCount(playerA, "Ephemeral Shields", 1); - assertPermanentCount(playerA, "Silvercoat Lion", 1); // was indestructible + assertPermanentCount(playerA, "Silvercoat Lion", 1); assertPermanentCount(playerA, "Oreskos Swiftclaw", 1); - - for (Permanent permanent: currentGame.getBattlefield().getAllActivePermanents(new FilterLandPermanent(), playerA.getId(), currentGame)) { - Assert.assertTrue(permanent.getName() + " may not be tapped", !permanent.isTapped()); - } } @Test - @Ignore - public void testConvokeTwoCreaturesOneWithProtection() { - /** - * Ephemeral Shields {1}{W} - * Instant - * Convoke (Your creatures can help cast this spell. Each creature you tap while casting this spell pays for or one mana of that creature's color.) - * Target creature gains indestructible until end of turn. (Damage and effects that say "destroy" don't destroy it.) - */ - - addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); // must be added because getPlayable does not take Convoke into account - + public void test_Other_ConvokeProtection() { + // {1}{W} + // Convoke (Your creatures can help cast this spell. Each creature you tap while casting this spell pays for or one mana of that creature's color.) + // Target creature gains indestructible until end of turn. (Damage and effects that say "destroy" don't destroy it.) + addCard(Zone.HAND, playerA, "Ephemeral Shields"); addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); + // Protection from white addCard(Zone.BATTLEFIELD, playerA, "Black Knight", 1); - addCard(Zone.HAND, playerA, "Ephemeral Shields"); - - addCard(Zone.BATTLEFIELD, playerB, "Mountain", 1); - addCard(Zone.HAND, playerB, "Lightning Bolt"); - - + // convoke must be able to target card with protection (it's no target) castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ephemeral Shields", "Silvercoat Lion"); - setChoice(playerA, "Yes"); - addTarget(playerA, "Silvercoat Lion^Black Knight"); + addTarget(playerA, "Silvercoat Lion"); + addTarget(playerA, "Black Knight"); - castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Lightning Bolt", "Silvercoat Lion"); + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); + assertAllCommandsUsed(); assertLife(playerA, 20); assertLife(playerB, 20); - - assertGraveyardCount(playerB, "Lightning Bolt", 1); - - assertGraveyardCount(playerA, "Ephemeral Shields", 1); - assertPermanentCount(playerA, "Silvercoat Lion", 1); // was indestructible - assertPermanentCount(playerA, "Black Knight", 1); - assertTapped("Silvercoat Lion", true); - assertTapped("Black Knight", true); - - for (Permanent permanent: currentGame.getBattlefield().getAllActivePermanents(new FilterLandPermanent(), playerA.getId(), currentGame)) { - Assert.assertTrue(permanent.getName() + " may not be tapped", !permanent.isTapped()); - } } @Test @Ignore - public void testConvokeFromChiefEngineer() { - /** - * Chief Engineer {1}{U} - * Creature - Vedalken, Artificer - * Artifact spells you cast have convoke. - */ - - // THIS TEST IS NOT FINISHED - addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); - addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); // creatures to use for convoek + // TODO: fix gain ability for spells to apply in all zones instead stack only or change getPlayable to look ahead and simulate spell on stack (wtf) + public void test_Other_ConvokeAsGains() { + // {1}{U} + // Artifact spells you cast have convoke. addCard(Zone.BATTLEFIELD, playerA, "Chief Engineer", 1); - - addCard(Zone.HAND, playerA, "ARTIFACT TO CAST", 1); + // + // {2} + addCard(Zone.HAND, playerA, "Alpha Myr", 1); + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 2); - - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "ARTIFACT TO CAST"); - setChoice(playerA, "Yes"); + // Chief Engineer gives convoke to Alpha Myr and xmage must see it as playable before put real spell to stack + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Alpha Myr"); + addTarget(playerA, "Silvercoat Lion"); addTarget(playerA, "Silvercoat Lion"); + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); + assertAllCommandsUsed(); - assertLife(playerA, 20); - assertLife(playerB, 20); - - + assertPermanentCount(playerA, "Alpha Myr", 1); } + @Test + @Ignore + // I don't know how to test it by framework - manual test works fine for HumanPlayer + // (he get warning message and can't activate mana abilities after convoke) + public void test_Other_CantUseConvokeBeforeManaAbilities() { + // https://github.com/magefree/mage/issues/768 + + // {6} + // Convoke + addCard(Zone.HAND, playerA, "Will-Forged Golem", 1); + // + // {2}{G} + // Create two 1/1 colorless Eldrazi Scion creature tokens. They have “Sacrifice this creature: Add {C}.” + addCard(Zone.HAND, playerA, "Call the Scions", 2); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3 * 2); + + // prepare scions + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Call the Scions"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Call the Scions"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + checkPermanentCount("scions", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Eldrazi Scion", 4); + + // test case 1 - playable abilities must not show it as playable (not work, cause we don't known real payment order before payment) + //checkPlayableAbility("can't use convoke", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Will-Forged Golem", false); + + // test case 2 - it's in playable list, but mana abilities can't be activated after convoke pay + //castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Will-Forged Golem"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } } \ No newline at end of file diff --git a/Mage/src/main/java/mage/abilities/keyword/ConvokeAbility.java b/Mage/src/main/java/mage/abilities/keyword/ConvokeAbility.java index 19a50c3e328..3aa290bba50 100644 --- a/Mage/src/main/java/mage/abilities/keyword/ConvokeAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/ConvokeAbility.java @@ -1,17 +1,17 @@ package mage.abilities.keyword; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.UUID; import mage.Mana; import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.SpecialAction; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.ActivationManaAbilityStep; import mage.abilities.costs.mana.AlternateManaPaymentAbility; import mage.abilities.costs.mana.ManaCost; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; import mage.abilities.effects.OneShotEffect; +import mage.abilities.hint.ValueHint; +import mage.abilities.mana.ManaOptions; import mage.choices.Choice; import mage.choices.ChoiceColor; import mage.constants.AbilityType; @@ -19,62 +19,67 @@ import mage.constants.ManaType; import mage.constants.Outcome; import mage.constants.Zone; import mage.filter.common.FilterControlledCreaturePermanent; -import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.ColorPredicate; import mage.filter.predicate.permanent.TappedPredicate; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; +import mage.game.stack.Spell; import mage.players.ManaPool; import mage.players.Player; import mage.target.Target; import mage.target.common.TargetControlledCreaturePermanent; -/* +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.UUID; + +/** * 502.46. Convoke - * + *
* 502.46a Convoke is a static ability that functions while the spell is on the stack. "Convoke" * means "As an additional cost to play this spell, you may tap any number of untapped creatures * you control. Each creature tapped this way reduces the cost to play this spell by {1} or by * one mana of any of that creature's colors." Using the convoke ability follows the rules for * paying additional costs in rules 409.1b and 4091f-h. - * + *
* Example: You play Guardian of Vitu-Ghazi, a spell with convoke that costs {3}{G}{W}. You announce * that you're going to tap an artifact creature, a red creature, and a green-and-white creature to * help pay for it. The artifact creature and the red creature each reduce the spell's cost by {1}. * You choose whether the green-white creature reduces the spell's cost by {1}, {G}, or {W}. Then * the creatures become tapped as you pay Guardian of Vitu-Ghazi's cost. - * + *
* 502.46b Convoke can't reduce the cost to play a spell to less than 0. - * + *
* 502.46c Multiple instances of convoke on the same spell are redundant. - * + *
* You can tap only untapped creatures you control to reduce the cost of a spell with convoke * that you play. - * + *
* While playing a spell with convoke, if you control a creature that taps to produce mana, you * can either tap it for mana or tap it to reduce the cost of the spell, but not both. - * + *
* If you tap a multicolored creature to reduce the cost of a spell with convoke, you reduce * the cost by {1} or by one mana of your choice of any of that creature's colors. - * + *
* Convoke doesn't change a spell's mana cost or converted mana cost. * - * - * @author LevelX2 + * @author LevelX2, JayDi85 */ public class ConvokeAbility extends SimpleStaticAbility implements AlternateManaPaymentAbility { - private static final FilterCreaturePermanent filterUntapped = new FilterCreaturePermanent(); + private static final FilterControlledCreaturePermanent filterUntapped = new FilterControlledCreaturePermanent(); static { filterUntapped.add(Predicates.not(TappedPredicate.instance)); } public ConvokeAbility() { - super(Zone.STACK, null); + super(Zone.ALL, null); // all AlternateManaPaymentAbility must use ALL zone to calculate playable abilities this.setRuleAtTheTop(true); + this.addHint(new ValueHint("Untapped creatures you control", new PermanentsOnBattlefieldCount(filterUntapped))); } public ConvokeAbility(final ConvokeAbility ability) { @@ -86,14 +91,25 @@ public class ConvokeAbility extends SimpleStaticAbility implements AlternateMana return new ConvokeAbility(this); } + @Override + public String getRule() { + return "Convoke (Your creatures can help cast this spell. Each creature you tap while casting this spell pays for {1} or one mana of that creature's color.)"; + } + + @Override + public ActivationManaAbilityStep useOnActivationManaAbilityStep() { + return ActivationManaAbilityStep.AFTER; + } + @Override public void addSpecialAction(Ability source, Game game, ManaCost unpaid) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null && game.getBattlefield().contains(filterUntapped, controller.getId(), 1, game)) { if (source.getAbilityType() == AbilityType.SPELL) { - SpecialAction specialAction = new ConvokeSpecialAction(unpaid); + SpecialAction specialAction = new ConvokeSpecialAction(unpaid, this); specialAction.setControllerId(source.getControllerId()); specialAction.setSourceId(source.getSourceId()); + // create filter for possible creatures to tap FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(); filter.add(Predicates.not(TappedPredicate.instance)); @@ -117,7 +133,7 @@ public class ConvokeAbility extends SimpleStaticAbility implements AlternateMana filter.add(Predicates.or(colorPredicates)); } Target target = new TargetControlledCreaturePermanent(1, 1, filter, true); - target.setTargetName("creature to convoke"); + target.setTargetName("tap creature card as convoke's pay"); specialAction.addTarget(target); if (specialAction.canActivate(source.getControllerId(), game).canActivate()) { game.getState().getSpecialActions().add(specialAction); @@ -127,15 +143,36 @@ public class ConvokeAbility extends SimpleStaticAbility implements AlternateMana } @Override - public String getRule() { - return "Convoke (Your creatures can help cast this spell. Each creature you tap while casting this spell pays for {1} or one mana of that creature's color.)"; + public ManaOptions getManaOptions(Ability source, Game game, ManaCost unpaid) { + ManaOptions options = new ManaOptions(); + FilterControlledCreaturePermanent filterBasic = new FilterControlledCreaturePermanent(); + + // each creature can give {1} or color mana + game.getBattlefield().getActivePermanents(filterBasic, source.getControllerId(), source.getSourceId(), game) + .stream() + .filter(permanent -> !permanent.isTapped()) + .forEach(permanent -> { + ManaOptions permMana = new ManaOptions(); + permMana.add(Mana.GenericMana(1)); + for (ObjectColor color : permanent.getColor(game).getColors()) { + if (color.isBlack()) permMana.add(Mana.BlackMana(1)); + if (color.isBlue()) permMana.add(Mana.BlueMana(1)); + if (color.isGreen()) permMana.add(Mana.GreenMana(1)); + if (color.isRed()) permMana.add(Mana.RedMana(1)); + if (color.isWhite()) permMana.add(Mana.WhiteMana(1)); + } + options.addMana(permMana); + }); + + options.removeDuplicated(); + return options; } } class ConvokeSpecialAction extends SpecialAction { - public ConvokeSpecialAction(ManaCost unpaid) { - super(Zone.ALL, true); + public ConvokeSpecialAction(ManaCost unpaid, AlternateManaPaymentAbility manaAbility) { + super(Zone.ALL, manaAbility); setRuleVisible(false); this.addEffect(new ConvokeEffect(unpaid)); } @@ -157,7 +194,7 @@ class ConvokeEffect extends OneShotEffect { public ConvokeEffect(ManaCost unpaid) { super(Outcome.Benefit); this.unpaid = unpaid; - this.staticText = "Convoke (Your creatures can help cast this spell. Each creature you tap while casting this spell pays for {C} or one mana of that creature's color.)"; + this.staticText = "Convoke (Your creatures can help cast this spell. Each creature you tap while casting this spell pays for {1} or one mana of that creature's color.)"; } public ConvokeEffect(final ConvokeEffect effect) { @@ -173,7 +210,8 @@ class ConvokeEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { + Spell spell = game.getStack().getSpell(source.getSourceId()); + if (controller != null && spell != null) { for (UUID creatureId : this.getTargetPointer().getTargets(game, source)) { Permanent perm = game.getPermanent(creatureId); if (perm == null) { @@ -225,8 +263,10 @@ class ConvokeEffect extends OneShotEffect { } game.fireEvent(GameEvent.getEvent(GameEvent.EventType.CONVOKED, perm.getId(), source.getSourceId(), source.getControllerId())); game.informPlayers("Convoke: " + controller.getLogName() + " taps " + perm.getLogName() + " to pay one " + manaName + " mana"); - } + // can't use mana abilities after that (convoke cost must be payed after mana abilities only) + spell.setCurrentActivatingManaAbilitiesStep(ActivationManaAbilityStep.AFTER); + } } return true; }