diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ImproviseTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ImproviseTest.java new file mode 100644 index 00000000000..e0712c2c416 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ImproviseTest.java @@ -0,0 +1,79 @@ +package org.mage.test.cards.abilities.keywords; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps; + +/** + * @author JayDi85 + */ + +public class ImproviseTest extends CardTestPlayerBaseWithAIHelps { + + // no simple playable tests for improvise, it's same as ConvokeTest + + @Test + public void test_PlayImprovise_Manual() { + // {5}{U} creature + // Improvise (Your artifacts can help cast this spell. Each artifact you tap after you’re done activating mana abilities pays for {1}.) + addCard(Zone.HAND, playerA, "Bastion Inventor", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + addCard(Zone.BATTLEFIELD, playerA, "Alpha Myr", 2); // improvise pay + + // use special action to pay (need disabled auto-payment and prepared mana pool) + disableManaAutoPayment(playerA); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {U}", 4); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bastion Inventor"); + setChoice(playerA, "Blue", 4); // pay 1-4 + // improvise pay by one card + setChoice(playerA, "Improvise"); + addTarget(playerA, "Alpha Myr"); // pay 5 as improvise + setChoice(playerA, "Improvise"); + addTarget(playerA, "Alpha Myr"); // pay 6 as improvise + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Bastion Inventor", 1); + } + + @Test + public void test_PlayImprovise_AI_AutoPay() { + // {5}{U} creature + // Improvise (Your artifacts can help cast this spell. Each artifact you tap after you’re done activating mana abilities pays for {1}.) + addCard(Zone.HAND, playerA, "Bastion Inventor", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + addCard(Zone.BATTLEFIELD, playerA, "Alpha Myr", 2); // improvise pay + + // AI must use special actions to pay as delve + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bastion Inventor"); + + //setStrictChooseMode(true); AI must choose targets + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Bastion Inventor", 1); + } + + @Test + public void test_PlayImprovise_AI_FullPlay() { + // {5}{U} creature + // Improvise (Your artifacts can help cast this spell. Each artifact you tap after you’re done activating mana abilities pays for {1}.) + addCard(Zone.HAND, playerA, "Bastion Inventor", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + addCard(Zone.BATTLEFIELD, playerA, "Alpha Myr", 2); // improvise pay + + aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Bastion Inventor", 1); + } +} \ No newline at end of file diff --git a/Mage/src/main/java/mage/abilities/keyword/ImproviseAbility.java b/Mage/src/main/java/mage/abilities/keyword/ImproviseAbility.java index 74181458175..5d7d15a4e36 100644 --- a/Mage/src/main/java/mage/abilities/keyword/ImproviseAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/ImproviseAbility.java @@ -1,23 +1,21 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ package mage.abilities.keyword; -import java.util.UUID; import mage.Mana; 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.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; import mage.abilities.effects.OneShotEffect; +import mage.abilities.hint.ValueHint; +import mage.abilities.mana.ManaOptions; import mage.constants.AbilityType; import mage.constants.ManaType; import mage.constants.Outcome; import mage.constants.Zone; -import mage.filter.common.FilterArtifactPermanent; import mage.filter.common.FilterControlledArtifactPermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.permanent.TappedPredicate; @@ -29,20 +27,36 @@ import mage.players.Player; import mage.target.Target; import mage.target.common.TargetControlledPermanent; +import java.util.UUID; + /** - * @author LevelX2 + * 702.125. Improvise + *

+ * 702.125a Improvise is a static ability that functions while the spell with improvise is on the stack. “Improvise” + * means “For each generic mana in this spell’s total cost, you may tap an untapped artifact you control rather + * than pay that mana.” + *

+ * 702.125b The improvise ability isn’t an additional or alternative cost and applies only after the total cost of + * the spell with improvise is determined. + *

+ * 702.125c Multiple instances of improvise on the same spell are redundant. + * + * @author LevelX2, JayDi85 */ public class ImproviseAbility extends SimpleStaticAbility implements AlternateManaPaymentAbility { - private static final FilterArtifactPermanent filterUntapped = new FilterArtifactPermanent(); + private static final FilterControlledArtifactPermanent filterUntapped = new FilterControlledArtifactPermanent("untapped artifact you control"); static { filterUntapped.add(Predicates.not(TappedPredicate.instance)); } + private static final DynamicValue untappedCount = new PermanentsOnBattlefieldCount(filterUntapped); + public ImproviseAbility() { - super(Zone.STACK, null); + super(Zone.ALL, null); this.setRuleAtTheTop(true); + this.addHint(new ValueHint("Untapped artifacts you control", untappedCount)); } public ImproviseAbility(final ImproviseAbility ability) { @@ -54,19 +68,29 @@ public class ImproviseAbility extends SimpleStaticAbility implements AlternateMa return new ImproviseAbility(this); } + + @Override + public String getRule() { + return "Improvise (Your artifacts can help cast this spell. Each artifact you tap after you're done activating mana abilities pays for {1}.)"; + } + + @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)) { + int canPayCount = untappedCount.calculate(game, source, null); + if (controller != null && canPayCount > 0) { if (source.getAbilityType() == AbilityType.SPELL && unpaid.getMana().getGeneric() > 0) { - SpecialAction specialAction = new ImproviseSpecialAction(unpaid); + SpecialAction specialAction = new ImproviseSpecialAction(unpaid, this); specialAction.setControllerId(source.getControllerId()); specialAction.setSourceId(source.getSourceId()); // create filter for possible artifacts to tap - FilterControlledArtifactPermanent filter = new FilterControlledArtifactPermanent(); - filter.add(Predicates.not(TappedPredicate.instance)); - Target target = new TargetControlledPermanent(1, unpaid.getMana().getGeneric(), filter, true); - target.setTargetName("artifact to Improvise"); + Target target = new TargetControlledPermanent(1, unpaid.getMana().getGeneric(), filterUntapped, true); + target.setTargetName("artifact to tap as Improvise's pay"); specialAction.addTarget(target); if (specialAction.canActivate(source.getControllerId(), game).canActivate()) { game.getState().getSpecialActions().add(specialAction); @@ -76,15 +100,21 @@ public class ImproviseAbility extends SimpleStaticAbility implements AlternateMa } @Override - public String getRule() { - return "Improvise (Your artifacts can help cast this spell. Each artifact you tap after you're done activating mana abilities pays for {1}.)"; + public ManaOptions getManaOptions(Ability source, Game game, ManaCost unpaid) { + ManaOptions options = new ManaOptions(); + Player controller = game.getPlayer(source.getControllerId()); + int canPayCount = untappedCount.calculate(game, source, null); + if (controller != null && canPayCount > 0) { + options.addMana(Mana.GenericMana(Math.min(unpaid.getMana().getGeneric(), canPayCount))); + } + return options; } } class ImproviseSpecialAction extends SpecialAction { - public ImproviseSpecialAction(ManaCost unpaid) { - super(Zone.ALL, true); + public ImproviseSpecialAction(ManaCost unpaid, AlternateManaPaymentAbility manaAbility) { + super(Zone.ALL, manaAbility); setRuleVisible(false); this.addEffect(new ImproviseEffect(unpaid)); } @@ -136,7 +166,9 @@ class ImproviseEffect extends OneShotEffect { if (!game.isSimulation()) { game.informPlayers("Improvise: " + controller.getLogName() + " taps " + perm.getLogName() + " to pay {1}"); } - spell.setDoneActivatingManaAbilities(true); + + // can't use mana abilities after that (improvise cost must be payed after mana abilities only) + spell.setCurrentActivatingManaAbilitiesStep(ActivationManaAbilityStep.AFTER); } }