Additional and alternative costs improved:

* Now player must choose additional costs before ability's modes;
* Fixed broken kicker ability from ZNR (see comments from d4ca287f0f);
* Improved compatibility of additional cost with cost modification effects (fixed that optional multi-costs doesn't affected by cost modification);
* Improved compatibility of additional cost with alternative cost (some cards ignores additional cost on alternative usage, e.g. on play free);
This commit is contained in:
Oleg Agafonov 2020-09-13 09:56:55 +04:00
parent 586538a66c
commit 6e0c7e868c
17 changed files with 504 additions and 156 deletions

View file

@ -8,7 +8,7 @@ import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.FightTargetsEffect; import mage.abilities.effects.common.FightTargetsEffect;
import mage.abilities.effects.common.counter.AddCountersTargetEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect;
import mage.abilities.keyword.KickerAbility; import mage.abilities.keyword.KickerWithAnyNumberModesAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
@ -33,12 +33,11 @@ public final class InscriptionOfAbundance extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{G}"); super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{G}");
// Kicker {2}{G} // Kicker {2}{G}
this.addAbility(new KickerAbility(new ManaCostsImpl<>("{2}{G}"))); this.addAbility(new KickerWithAnyNumberModesAbility(new ManaCostsImpl<>("{2}{G}")));
// Choose one. If this spell was kicked, choose any number instead. // Choose one. If this spell was kicked, choose any number instead.
this.getSpellAbility().getModes().setAllWhenKicked(true);
// Put two +1/+1 counters on target creature. // Put two +1/+1 counters on target creature.
this.getSpellAbility().getModes().setChooseText("choose one. If this spell was kicked, choose any number instead.");
this.getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.P1P1.createInstance(2))); this.getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.P1P1.createInstance(2)));
this.getSpellAbility().addTarget(new TargetCreaturePermanent()); this.getSpellAbility().addTarget(new TargetCreaturePermanent());

View file

@ -7,7 +7,7 @@ import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.ReturnToHandTargetEffect; import mage.abilities.effects.common.ReturnToHandTargetEffect;
import mage.abilities.effects.keyword.ScryEffect; import mage.abilities.effects.keyword.ScryEffect;
import mage.abilities.keyword.KickerAbility; import mage.abilities.keyword.KickerWithAnyNumberModesAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
@ -29,12 +29,11 @@ public final class InscriptionOfInsight extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{U}"); super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{U}");
// Kicker {2}{U}{U} // Kicker {2}{U}{U}
this.addAbility(new KickerAbility(new ManaCostsImpl<>("{2}{U}{U}"))); this.addAbility(new KickerWithAnyNumberModesAbility(new ManaCostsImpl<>("{2}{U}{U}")));
// Choose one. If this spell was kicked, choose any number instead. // Choose one. If this spell was kicked, choose any number instead.
this.getSpellAbility().getModes().setAllWhenKicked(true);
// Return up to two target creatures to their owners' hands. // Return up to two target creatures to their owners' hands.
this.getSpellAbility().getModes().setChooseText("choose one. If this spell was kicked, choose any number instead.");
this.getSpellAbility().addEffect(new ReturnToHandTargetEffect()); this.getSpellAbility().addEffect(new ReturnToHandTargetEffect());
this.getSpellAbility().addTarget(new TargetCreaturePermanent(0, 2)); this.getSpellAbility().addTarget(new TargetCreaturePermanent(0, 2));

View file

@ -5,7 +5,7 @@ import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.effects.common.DestroyTargetEffect;
import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect;
import mage.abilities.effects.common.discard.DiscardTargetEffect; import mage.abilities.effects.common.discard.DiscardTargetEffect;
import mage.abilities.keyword.KickerAbility; import mage.abilities.keyword.KickerWithAnyNumberModesAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
@ -40,12 +40,11 @@ public final class InscriptionOfRuin extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{B}"); super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{B}");
// Kicker {2}{B}{B} // Kicker {2}{B}{B}
this.addAbility(new KickerAbility(new ManaCostsImpl<>("{2}{B}{B}"))); this.addAbility(new KickerWithAnyNumberModesAbility(new ManaCostsImpl<>("{2}{B}{B}")));
// Choose one. If this spell was kicked, choose any number instead. // Choose one. If this spell was kicked, choose any number instead.
this.getSpellAbility().getModes().setAllWhenKicked(true);
// Target opponent discards two cards. // Target opponent discards two cards.
this.getSpellAbility().getModes().setChooseText("choose one. If this spell was kicked, choose any number instead.");
this.getSpellAbility().addEffect(new DiscardTargetEffect(2)); this.getSpellAbility().addEffect(new DiscardTargetEffect(2));
this.getSpellAbility().addTarget(new TargetOpponent()); this.getSpellAbility().addTarget(new TargetOpponent());

View file

@ -1,4 +1,3 @@
package org.mage.test.cards.abilities.keywords; package org.mage.test.cards.abilities.keywords;
import mage.constants.PhaseStep; import mage.constants.PhaseStep;
@ -7,39 +6,172 @@ import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase; import org.mage.test.serverside.base.CardTestPlayerBase;
/** /**
* * @author LevelX2, JayDi85
* @author LevelX2
*/ */
public class EntwineTest extends CardTestPlayerBase { public class EntwineTest extends CardTestPlayerBase {
@Test
public void test_CastWithoutEntwine() {
// Choose one
// Barbed Lightning deals 3 damage to target creature.
// Barbed Lightning deals 3 damage to target player or planeswalker.
// Entwine {2} (Choose both if you pay the entwine cost.)
addCard(Zone.HAND, playerA, "Barbed Lightning", 1); // {2}{R}
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
//
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); // 2/2
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Barbed Lightning");
setChoice(playerA, "No"); // not use Entwine
setModeChoice(playerA, "1"); // target creature
addTarget(playerA, "Balduvian Bears");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertLife(playerA, 20);
assertPermanentCount(playerA, "Balduvian Bears", 0);
assertTappedCount("Mountain", true, 3);
}
@Test
public void test_CastEntwine_Normal() {
// Choose one
// Barbed Lightning deals 3 damage to target creature.
// Barbed Lightning deals 3 damage to target player or planeswalker.
// Entwine {2} (Choose both if you pay the entwine cost.)
addCard(Zone.HAND, playerA, "Barbed Lightning", 1); // {2}{R}
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3 + 2);
//
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); // 2/2
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Barbed Lightning");
setChoice(playerA, "Yes"); // use Entwine
addTarget(playerA, "Balduvian Bears");
addTarget(playerA, playerA);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertLife(playerA, 20 - 3);
assertPermanentCount(playerA, "Balduvian Bears", 0);
assertTappedCount("Mountain", true, 3 + 2);
}
@Test
public void test_CastEntwine_CostReduction() {
addCustomEffect_SpellCostModification(playerA, -4);
// Choose one
// Barbed Lightning deals 3 damage to target creature.
// Barbed Lightning deals 3 damage to target player or planeswalker.
// Entwine {2} (Choose both if you pay the entwine cost.)
addCard(Zone.HAND, playerA, "Barbed Lightning", 1); // {2}{R}
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); // -4 as cost reduction
//
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); // 2/2
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Barbed Lightning");
setChoice(playerA, "Yes"); // use Entwine
addTarget(playerA, "Balduvian Bears");
addTarget(playerA, playerA);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertLife(playerA, 20 - 3);
assertPermanentCount(playerA, "Balduvian Bears", 0);
assertTappedCount("Mountain", true, 1);
}
@Test
public void test_CastEntwine_CostIncreasing() {
addCustomEffect_SpellCostModification(playerA, 5);
// Choose one
// Barbed Lightning deals 3 damage to target creature.
// Barbed Lightning deals 3 damage to target player or planeswalker.
// Entwine {2} (Choose both if you pay the entwine cost.)
addCard(Zone.HAND, playerA, "Barbed Lightning", 1); // {2}{R}
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3 + 2 + 5);
//
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); // 2/2
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Barbed Lightning");
setChoice(playerA, "Yes"); // use Entwine
addTarget(playerA, "Balduvian Bears");
addTarget(playerA, playerA);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertLife(playerA, 20 - 3);
assertPermanentCount(playerA, "Balduvian Bears", 0);
assertTappedCount("Mountain", true, 3 + 2 + 5);
}
@Test
public void test_CastEntwine_FreeFromHand() {
// You may cast nonland cards from your hand without paying their mana costs.
addCard(Zone.BATTLEFIELD, playerA, "Omniscience");
// Choose one
// Barbed Lightning deals 3 damage to target creature.
// Barbed Lightning deals 3 damage to target player or planeswalker.
// Entwine {2} (Choose both if you pay the entwine cost.)
addCard(Zone.HAND, playerA, "Barbed Lightning", 1); // {2}{R}
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); // only Entwine pay need
//
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); // 2/2
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Barbed Lightning");
setChoice(playerA, "Yes"); // cast for free
setChoice(playerA, "Yes"); // use Entwine
addTarget(playerA, "Balduvian Bears");
addTarget(playerA, playerA);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertLife(playerA, 20 - 3);
assertPermanentCount(playerA, "Balduvian Bears", 0);
assertTappedCount("Plains", true, 2);
}
@Test @Test
public void test_ToothAndNail() { public void test_ToothAndNail() {
setStrictChooseMode(true); setStrictChooseMode(true);
addCard(Zone.LIBRARY, playerA, "Silvercoat Lion", 1); addCard(Zone.LIBRARY, playerA, "Silvercoat Lion", 1);
addCard(Zone.LIBRARY, playerA, "Pillarfield Ox", 1); addCard(Zone.LIBRARY, playerA, "Pillarfield Ox", 1);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 9); addCard(Zone.BATTLEFIELD, playerA, "Forest", 9);
// Choose one - // Choose one -
// Search your library for up to two creature cards, reveal them, put them into your hand, then shuffle your library; // Search your library for up to two creature cards, reveal them, put them into your hand, then shuffle your library;
// or put up to two creature cards from your hand onto the battlefield. // or put up to two creature cards from your hand onto the battlefield.
// Entwine {2} // Entwine {2}
addCard(Zone.HAND, playerA, "Tooth and Nail"); // Sorcery {5}{G}{G} addCard(Zone.HAND, playerA, "Tooth and Nail"); // Sorcery {5}{G}{G}
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tooth and Nail"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tooth and Nail");
setChoice(playerA, "Yes"); // Message: Pay Entwine {2} ? setChoice(playerA, "Yes"); // Message: Pay Entwine {2} ?
addTarget(playerA, "Silvercoat Lion^Pillarfield Ox"); addTarget(playerA, "Silvercoat Lion^Pillarfield Ox");
setChoice(playerA, "Silvercoat Lion^Pillarfield Ox");
setChoice(playerA, "Silvercoat Lion^Pillarfield Ox");
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
execute(); execute();
assertAllCommandsUsed(); assertAllCommandsUsed();
assertPermanentCount(playerA, "Silvercoat Lion", 1); assertPermanentCount(playerA, "Silvercoat Lion", 1);
assertPermanentCount(playerA, "Pillarfield Ox", 1); assertPermanentCount(playerA, "Pillarfield Ox", 1);
} }

View file

@ -400,6 +400,7 @@ public class KickerTest extends CardTestPlayerBase {
assertTappedCount("Swamp", true, 5); assertTappedCount("Swamp", true, 5);
assertGraveyardCount(playerA, "Marsh Casualties", 1); assertGraveyardCount(playerA, "Marsh Casualties", 1);
assertPowerToughness(playerB, "Centaur Courser", 1, 1); assertPowerToughness(playerB, "Centaur Courser", 1, 1);
} }
} }

View file

@ -0,0 +1,158 @@
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.CardTestPlayerBase;
/**
* @author JayDi85
*/
public class KickerWithAnyNumberModesAbilityTest extends CardTestPlayerBase {
@Test
public void test_WithoutKicker() {
// Kicker {2}{G}
// Choose one. If this spell was kicked, choose any number instead.
// Put two +1/+1 counters on target creature.
// Target player gains X life, where X is the greatest power among creatures they control.
// Target creature you control fights target creature you don't control.
addCard(Zone.HAND, playerA, "Inscription of Abundance", 1); // {1}{G}
addCard(Zone.BATTLEFIELD, playerA, "Forest", 2);
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); // 2/2
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Inscription of Abundance");
setChoice(playerA, "No"); // no kicker
setModeChoice(playerA, "1");
addTarget(playerA, "Balduvian Bears");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, "Balduvian Bears", 2 + 2, 2 + 2);
assertLife(playerA, 20);
assertTappedCount("Forest", true, 2);
}
@Test
public void test_Kicker_Normal() {
// Kicker {2}{G}
// Choose one. If this spell was kicked, choose any number instead.
// Put two +1/+1 counters on target creature.
// Target player gains X life, where X is the greatest power among creatures they control.
// Target creature you control fights target creature you don't control.
addCard(Zone.HAND, playerA, "Inscription of Abundance", 1); // {1}{G}
addCard(Zone.BATTLEFIELD, playerA, "Forest", 2 + 3);
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); // 2/2
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Inscription of Abundance");
setChoice(playerA, "Yes"); // use kicker
setModeChoice(playerA, "2");
setModeChoice(playerA, "1");
addTarget(playerA, playerA); // gain x life
addTarget(playerA, "Balduvian Bears"); // get counters
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, "Balduvian Bears", 2 + 2, 2 + 2);
assertLife(playerA, 20 + 4);
assertTappedCount("Forest", true, 2 + 3);
}
@Test
public void test_Kicker_CostReduction() {
addCustomEffect_SpellCostModification(playerA, -4);
// Kicker {2}{G}
// Choose one. If this spell was kicked, choose any number instead.
// Put two +1/+1 counters on target creature.
// Target player gains X life, where X is the greatest power among creatures they control.
// Target creature you control fights target creature you don't control.
addCard(Zone.HAND, playerA, "Inscription of Abundance", 1); // {1}{G}
addCard(Zone.BATTLEFIELD, playerA, "Forest", 2 + 3 - 3); // -3 by cost reduction
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); // 2/2
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Inscription of Abundance");
setChoice(playerA, "Yes"); // use kicker
setModeChoice(playerA, "2");
setModeChoice(playerA, "1");
addTarget(playerA, playerA); // gain x life
addTarget(playerA, "Balduvian Bears"); // get counters
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, "Balduvian Bears", 2 + 2, 2 + 2);
assertLife(playerA, 20 + 4);
assertTappedCount("Forest", true, 2 + 3 - 3);
}
@Test
public void test_Kicker_CostIncreasing() {
addCustomEffect_SpellCostModification(playerA, 5);
// Kicker {2}{G}
// Choose one. If this spell was kicked, choose any number instead.
// Put two +1/+1 counters on target creature.
// Target player gains X life, where X is the greatest power among creatures they control.
// Target creature you control fights target creature you don't control.
addCard(Zone.HAND, playerA, "Inscription of Abundance", 1); // {1}{G}
addCard(Zone.BATTLEFIELD, playerA, "Forest", 2 + 3 + 5); // +5 by cost increase
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); // 2/2
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Inscription of Abundance");
setChoice(playerA, "Yes"); // use kicker
setModeChoice(playerA, "2");
setModeChoice(playerA, "1");
addTarget(playerA, playerA); // gain x life
addTarget(playerA, "Balduvian Bears"); // get counters
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, "Balduvian Bears", 2 + 2, 2 + 2);
assertLife(playerA, 20 + 4);
assertTappedCount("Forest", true, 2 + 3 + 5);
}
@Test
public void test_Kicker_FreeFromHand() {
// You may cast nonland cards from your hand without paying their mana costs.
addCard(Zone.BATTLEFIELD, playerA, "Omniscience");
// Kicker {2}{G}
// Choose one. If this spell was kicked, choose any number instead.
// Put two +1/+1 counters on target creature.
// Target player gains X life, where X is the greatest power among creatures they control.
// Target creature you control fights target creature you don't control.
addCard(Zone.HAND, playerA, "Inscription of Abundance", 1); // {1}{G}
addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); // -2 by free cast
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1); // 2/2
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Inscription of Abundance");
setChoice(playerA, "Yes"); // use free cast
setChoice(playerA, "Yes"); // use kicker
setModeChoice(playerA, "2");
setModeChoice(playerA, "1");
addTarget(playerA, playerA); // gain x life
addTarget(playerA, "Balduvian Bears"); // get counters
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPowerToughness(playerA, "Balduvian Bears", 2 + 2, 2 + 2);
assertLife(playerA, 20 + 4);
assertTappedCount("Forest", true, 3);
}
}

View file

@ -192,7 +192,7 @@ public class CastFromHandWithoutPayingManaCostTest extends CardTestPlayerBase {
/** /**
* Omniscience is not allowing me to cast spells for free. I'm playing a * Omniscience is not allowing me to cast spells for free. I'm playing a
* Commander game against the Computer, if that helps. * Commander game against the Computer, if that helps.
* * <p>
* Edit: It's not letting me cast fused spells for free. Others seems to be * Edit: It's not letting me cast fused spells for free. Others seems to be
* working. * working.
*/ */
@ -276,16 +276,23 @@ public class CastFromHandWithoutPayingManaCostTest extends CardTestPlayerBase {
// Choose one - Barbed Lightning deals 3 damage to target creature; or Barbed Lightning deals 3 damage to target player. // Choose one - Barbed Lightning deals 3 damage to target creature; or Barbed Lightning deals 3 damage to target player.
// Entwine {2} (Choose both if you pay the entwine cost.) // Entwine {2} (Choose both if you pay the entwine cost.)
addCard(Zone.HAND, playerA, "Barbed Lightning", 1); addCard(Zone.HAND, playerA, "Barbed Lightning", 1); // {2}{R}
// Creature - 3/3 Swampwalk // Creature - 3/3 Swampwalk
addCard(Zone.BATTLEFIELD, playerB, "Bog Wraith", 1); addCard(Zone.BATTLEFIELD, playerB, "Bog Wraith", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Barbed Lightning", "Bog Wraith"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Barbed Lightning");
addTarget(playerA, playerB); setChoice(playerA, "Yes"); // cast without cost
setChoice(playerA, "Yes"); // pay Entwine
addTarget(playerA, "Bog Wraith"); // target form mode 1
addTarget(playerA, playerB); // target for mode 2
setStopAt(1, PhaseStep.BEGIN_COMBAT); showBattlefield("after", 1, PhaseStep.POSTCOMBAT_MAIN, playerA);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute(); execute();
assertAllCommandsUsed();
assertGraveyardCount(playerA, "Barbed Lightning", 1); assertGraveyardCount(playerA, "Barbed Lightning", 1);
assertGraveyardCount(playerB, "Bog Wraith", 1); assertGraveyardCount(playerB, "Bog Wraith", 1);
@ -293,7 +300,7 @@ public class CastFromHandWithoutPayingManaCostTest extends CardTestPlayerBase {
assertLife(playerA, 20); assertLife(playerA, 20);
assertLife(playerB, 17); assertLife(playerB, 17);
assertTapped("Plains", true); // plains have to be tapped because {2} from Entwine have to be paid assertTappedCount("Plains", true, 2); // plains have to be tapped because {2} from Entwine have to be paid
} }
/** /**

View file

@ -1,4 +1,3 @@
package org.mage.test.cards.modal; package org.mage.test.cards.modal;
import mage.abilities.keyword.SwampwalkAbility; import mage.abilities.keyword.SwampwalkAbility;
@ -8,7 +7,6 @@ import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase; import org.mage.test.serverside.base.CardTestPlayerBase;
/** /**
*
* @author LevelX2 * @author LevelX2
*/ */
public class ChooseOneTest extends CardTestPlayerBase { public class ChooseOneTest extends CardTestPlayerBase {
@ -48,8 +46,10 @@ public class ChooseOneTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Funeral Charm", "Silvercoat Lion"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Funeral Charm", "Silvercoat Lion");
setModeChoice(playerA, "2"); setModeChoice(playerA, "2");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.BEGIN_COMBAT); setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute(); execute();
assertAllCommandsUsed();
assertGraveyardCount(playerA, "Funeral Charm", 1); assertGraveyardCount(playerA, "Funeral Charm", 1);
assertPowerToughness(playerB, "Silvercoat Lion", 4, 1); assertPowerToughness(playerB, "Silvercoat Lion", 4, 1);

View file

@ -1,9 +1,5 @@
package mage.abilities; package mage.abilities;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import mage.MageIdentifier; import mage.MageIdentifier;
import mage.MageObject; import mage.MageObject;
import mage.abilities.costs.*; import mage.abilities.costs.*;
@ -36,6 +32,11 @@ import mage.util.ThreadLocalStringBuilder;
import mage.watchers.Watcher; import mage.watchers.Watcher;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
/** /**
* @author BetaSteward_at_googlemail.com * @author BetaSteward_at_googlemail.com
*/ */
@ -227,22 +228,10 @@ public abstract class AbilityImpl implements Ability {
} }
game.applyEffects(); game.applyEffects();
/* 20130201 - 601.2b
* If the spell is modal the player announces the mode choice (see rule 700.2).
*/
if (!getModes().choose(game, this)) {
return false;
}
MageObject sourceObject = getSourceObject(game); MageObject sourceObject = getSourceObject(game);
if (getSourceObjectZoneChangeCounter() == 0) { if (getSourceObjectZoneChangeCounter() == 0) {
setSourceObjectZoneChangeCounter(game.getState().getZoneChangeCounter(getSourceId())); setSourceObjectZoneChangeCounter(game.getState().getZoneChangeCounter(getSourceId()));
} }
if (controller.isTestMode()) {
if (!controller.addTargets(this, game)) {
return false;
}
}
/* 20130201 - 601.2b /* 20130201 - 601.2b
* If the player wishes to splice any cards onto the spell (see rule 702.45), he * If the player wishes to splice any cards onto the spell (see rule 702.45), he
@ -268,9 +257,6 @@ public abstract class AbilityImpl implements Ability {
} }
} }
if (getModes().getAdditionalCost() != null) {
getModes().getAdditionalCost().addOptionalAdditionalModeCosts(this, game);
}
// 20130201 - 601.2b // 20130201 - 601.2b
// If the spell has alternative or additional costs that will be paid as it's being cast such // If the spell has alternative or additional costs that will be paid as it's being cast such
// as buyback, kicker, or convoke costs (see rules 117.8 and 117.9), the player announces his // as buyback, kicker, or convoke costs (see rules 117.8 and 117.9), the player announces his
@ -311,8 +297,29 @@ public abstract class AbilityImpl implements Ability {
handlePhyrexianManaCosts(game, sourceId, controller); handlePhyrexianManaCosts(game, sourceId, controller);
/* 20130201 - 601.2b
* If the spell is modal the player announces the mode choice (see rule 700.2).
*/
// rules:
// You kick a spell as you cast it. You declare whether you're going to pay a kicker cost at the same
// time you'd choose a spell's mode, and then you actually pay it at the same time you pay the spell's mana cost.
// Kicking a spell is always optional.
if (!getModes().choose(game, this)) {
return false;
}
// unit tests only: it allows to add targets/choices by two ways:
// 1. From cast/activate command params (process it here)
// 2. From single addTarget/setChoice, it's a preffered method for tests (process it in normal choose dialogs like human player)
if (controller.isTestMode()) {
if (!controller.addTargets(this, game)) {
return false;
}
}
for (UUID modeId : this.getModes().getSelectedModes()) { for (UUID modeId : this.getModes().getSelectedModes()) {
this.getModes().setActiveMode(modeId); this.getModes().setActiveMode(modeId);
//20121001 - 601.2c //20121001 - 601.2c
// 601.2c The player announces their choice of an appropriate player, object, or zone for // 601.2c The player announces their choice of an appropriate player, object, or zone for
// each target the spell requires. A spell may require some targets only if an alternative or // each target the spell requires. A spell may require some targets only if an alternative or
@ -333,8 +340,10 @@ public abstract class AbilityImpl implements Ability {
if (sourceObject != null && this.getAbilityType() != AbilityType.TRIGGERED) { // triggered abilities check this already in playerImpl.triggerAbility if (sourceObject != null && this.getAbilityType() != AbilityType.TRIGGERED) { // triggered abilities check this already in playerImpl.triggerAbility
sourceObject.adjustTargets(this, game); sourceObject.adjustTargets(this, game);
} }
if (!getTargets().isEmpty()) { if (!getTargets().isEmpty()) {
Outcome outcome = getEffects().getOutcome(this); Outcome outcome = getEffects().getOutcome(this);
// only activated abilities can be canceled by human user (not triggered) // only activated abilities can be canceled by human user (not triggered)
boolean canCancel = this instanceof ActivatedAbility && controller.isHuman(); boolean canCancel = this instanceof ActivatedAbility && controller.isHuman();
if (!getTargets().chooseTargets(outcome, this.controllerId, this, noMana, game, canCancel)) { if (!getTargets().chooseTargets(outcome, this.controllerId, this, noMana, game, canCancel)) {
@ -434,7 +443,11 @@ public abstract class AbilityImpl implements Ability {
boolean alternativeCostUsed = false; boolean alternativeCostUsed = false;
if (sourceObject != null && !(sourceObject instanceof Permanent)) { if (sourceObject != null && !(sourceObject instanceof Permanent)) {
// it's important to apply alternative cost first
// example: Omniscience gives free mana as alternative, but Entwine ability adds {2} as additional
Abilities<Ability> abilities = CardUtil.getAbilities(sourceObject, game); Abilities<Ability> abilities = CardUtil.getAbilities(sourceObject, game);
// 1. ALTERNATIVE COSTS
for (Ability ability : abilities) { for (Ability ability : abilities) {
// if cast for noMana no Alternative costs are allowed // if cast for noMana no Alternative costs are allowed
if (canUseAlternativeCost && !noMana && ability instanceof AlternativeSourceCosts) { if (canUseAlternativeCost && !noMana && ability instanceof AlternativeSourceCosts) {
@ -447,11 +460,7 @@ public abstract class AbilityImpl implements Ability {
} }
} }
} }
if (canUseAdditionalCost && ability instanceof OptionalAdditionalSourceCosts) {
((OptionalAdditionalSourceCosts) ability).addOptionalAdditionalCosts(this, game);
}
} }
// controller specific alternate spell costs // controller specific alternate spell costs
if (canUseAlternativeCost && !noMana && !alternativeCostUsed) { if (canUseAlternativeCost && !noMana && !alternativeCostUsed) {
if (this.getAbilityType() == AbilityType.SPELL if (this.getAbilityType() == AbilityType.SPELL
@ -469,6 +478,13 @@ public abstract class AbilityImpl implements Ability {
} }
} }
} }
// 2. ADDITIONAL COST
for (Ability ability : abilities) {
if (canUseAdditionalCost && ability instanceof OptionalAdditionalSourceCosts) {
((OptionalAdditionalSourceCosts) ability).addOptionalAdditionalCosts(this, game);
}
}
} }
return alternativeCostUsed; return alternativeCostUsed;
@ -806,7 +822,18 @@ public abstract class AbilityImpl implements Ability {
@Override @Override
public void addCost(Cost cost) { public void addCost(Cost cost) {
if (cost != null) { if (cost == null) {
return;
}
if (cost instanceof Costs) {
// as list of costs
Costs<Cost> list = (Costs<Cost>) cost;
for (Cost single : list) {
addCost(single);
}
} else {
// as single cost
if (cost instanceof ManaCost) { if (cost instanceof ManaCost) {
this.addManaCost((ManaCost) cost); this.addManaCost((ManaCost) cost);
} else { } else {

View file

@ -1,6 +1,5 @@
package mage.abilities; package mage.abilities;
import mage.abilities.condition.common.KickedCondition;
import mage.abilities.costs.OptionalAdditionalModeSourceCosts; import mage.abilities.costs.OptionalAdditionalModeSourceCosts;
import mage.cards.Card; import mage.cards.Card;
import mage.constants.Outcome; import mage.constants.Outcome;
@ -33,11 +32,9 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
private TargetController modeChooser; private TargetController modeChooser;
private boolean eachModeMoreThanOnce; // each mode can be selected multiple times during one choice private boolean eachModeMoreThanOnce; // each mode can be selected multiple times during one choice
private boolean eachModeOnlyOnce; // state if each mode can be chosen only once as long as the source object exists private boolean eachModeOnlyOnce; // state if each mode can be chosen only once as long as the source object exists
private OptionalAdditionalModeSourceCosts optionalAdditionalModeSourceCosts = null; // only set if costs have to be paid
private Filter maxModesFilter = null; // calculates the max number of available modes private Filter maxModesFilter = null; // calculates the max number of available modes
private boolean isRandom = false; private boolean isRandom = false;
private String chooseText = null; private String chooseText = null;
private boolean allWhenKicked = false;
private boolean resetEachTurn = false; private boolean resetEachTurn = false;
private int turnNum = 0; private int turnNum = 0;
@ -68,12 +65,10 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
this.modeChooser = modes.modeChooser; this.modeChooser = modes.modeChooser;
this.eachModeOnlyOnce = modes.eachModeOnlyOnce; this.eachModeOnlyOnce = modes.eachModeOnlyOnce;
this.eachModeMoreThanOnce = modes.eachModeMoreThanOnce; this.eachModeMoreThanOnce = modes.eachModeMoreThanOnce;
this.optionalAdditionalModeSourceCosts = modes.optionalAdditionalModeSourceCosts;
this.maxModesFilter = modes.maxModesFilter; // can't change so no copy needed this.maxModesFilter = modes.maxModesFilter; // can't change so no copy needed
this.isRandom = modes.isRandom; this.isRandom = modes.isRandom;
this.chooseText = modes.chooseText; this.chooseText = modes.chooseText;
this.allWhenKicked = modes.allWhenKicked;
this.resetEachTurn = modes.resetEachTurn; this.resetEachTurn = modes.resetEachTurn;
this.turnNum = modes.turnNum; this.turnNum = modes.turnNum;
if (modes.getSelectedModes().isEmpty()) { if (modes.getSelectedModes().isEmpty()) {
@ -228,15 +223,6 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
this.turnNum = game.getTurnNum(); this.turnNum = game.getTurnNum();
} }
} }
if (this.allWhenKicked) {
if (KickedCondition.instance.apply(game, source)) {
this.setMinModes(0);
this.setMaxModes(3);
} else {
this.setMinModes(1);
this.setMaxModes(1);
}
}
if (this.size() > 1) { if (this.size() > 1) {
this.clearSelectedModes(); this.clearSelectedModes();
if (this.isRandom) { if (this.isRandom) {
@ -244,15 +230,18 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
this.addSelectedMode(modes.get(RandomUtil.nextInt(modes.size())).getId()); this.addSelectedMode(modes.get(RandomUtil.nextInt(modes.size())).getId());
return true; return true;
} }
// check if mode modifying abilities exist // check if mode modifying abilities exist
Card card = game.getCard(source.getSourceId()); Card card = game.getCard(source.getSourceId());
if (card != null) { if (card != null) {
for (Ability modeModifyingAbility : card.getAbilities()) { for (Ability modeModifyingAbility : card.getAbilities(game)) {
if (modeModifyingAbility instanceof OptionalAdditionalModeSourceCosts) { if (modeModifyingAbility instanceof OptionalAdditionalModeSourceCosts) {
// cost must check activation conditional in changeModes
((OptionalAdditionalModeSourceCosts) modeModifyingAbility).changeModes(source, game); ((OptionalAdditionalModeSourceCosts) modeModifyingAbility).changeModes(source, game);
} }
} }
} }
// check if all modes can be activated automatically // check if all modes can be activated automatically
if (this.size() == this.getMinModes() && !isEachModeMoreThanOnce()) { if (this.size() == this.getMinModes() && !isEachModeMoreThanOnce()) {
Set<UUID> onceSelectedModes = null; Set<UUID> onceSelectedModes = null;
@ -434,8 +423,6 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
if (this.chooseText != null) { if (this.chooseText != null) {
sb.append(chooseText); sb.append(chooseText);
} else if (this.allWhenKicked) {
sb.append("choose one. If this spell was kicked, choose any number instead.");
} else if (this.getMaxModesFilter() != null) { } else if (this.getMaxModesFilter() != null) {
sb.append("choose one or more. Each mode must target ").append(getMaxModesFilter().getMessage()); sb.append("choose one or more. Each mode must target ").append(getMaxModesFilter().getMessage());
} else if (this.getMinModes() == 0 && this.getMaxModes() == 1) { } else if (this.getMinModes() == 0 && this.getMaxModes() == 1) {
@ -499,22 +486,10 @@ public class Modes extends LinkedHashMap<UUID, Mode> {
this.eachModeMoreThanOnce = eachModeMoreThanOnce; this.eachModeMoreThanOnce = eachModeMoreThanOnce;
} }
public OptionalAdditionalModeSourceCosts getAdditionalCost() {
return optionalAdditionalModeSourceCosts;
}
public void setAdditionalCost(OptionalAdditionalModeSourceCosts optionalAdditionalModeSourceCosts) {
this.optionalAdditionalModeSourceCosts = optionalAdditionalModeSourceCosts;
}
public void setRandom(boolean isRandom) { public void setRandom(boolean isRandom) {
this.isRandom = isRandom; this.isRandom = isRandom;
} }
public void setAllWhenKicked(boolean allWhenKicked) {
this.allWhenKicked = allWhenKicked;
}
public boolean isResetEachTurn() { public boolean isResetEachTurn() {
return resetEachTurn; return resetEachTurn;
} }

View file

@ -1,4 +1,3 @@
package mage.abilities.costs; package mage.abilities.costs;
import java.util.ArrayList; import java.util.ArrayList;
@ -161,5 +160,4 @@ public class CostsImpl<T extends Cost> extends ArrayList<T> implements Costs<T>
public Costs<T> copy() { public Costs<T> copy() {
return new CostsImpl(this); return new CostsImpl(this);
} }
} }

View file

@ -1,10 +1,11 @@
package mage.abilities.costs; package mage.abilities.costs;
import mage.util.Copyable;
/** /**
* @author LevelX2 * @author LevelX2
*/ */
public interface OptionalAdditionalCost extends Cost { public interface OptionalAdditionalCost extends Cost, Copyable<OptionalAdditionalCost> {
String getName(); String getName();
@ -29,20 +30,18 @@ public interface OptionalAdditionalCost extends Cost {
* message. * message.
* *
* @param position - if there are multiple costs, it's the postion the cost * @param position - if there are multiple costs, it's the postion the cost
* is set (starting with 0) * is set (starting with 0)
* @return * @return
*/ */
String getCastSuffixMessage(int position); String getCastSuffixMessage(int position);
/** /**
* If the player intends to pay the cost, the cost will be activated * If the player intends to pay the cost, the cost will be activated
*
*/ */
void activate(); void activate();
/** /**
* Reset the activate and count information * Reset the activate and count information
*
*/ */
void reset(); void reset();

View file

@ -1,18 +1,15 @@
package mage.abilities.costs; package mage.abilities.costs;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.game.Game; import mage.game.Game;
/** /**
* Cost that can change ability's modes (example: Kicker or Entwine can make all modes selectabled).
* *
* @author LevelX2 * @author LevelX2
*/ */
public interface OptionalAdditionalModeSourceCosts { public interface OptionalAdditionalModeSourceCosts extends OptionalAdditionalSourceCosts {
void addOptionalAdditionalModeCosts(Ability ability, Game game);
void changeModes(Ability ability, Game game); void changeModes(Ability ability, Game game);
String getCastMessageSuffix();
} }

View file

@ -3,14 +3,19 @@ package mage.abilities.keyword;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.SpellAbility; import mage.abilities.SpellAbility;
import mage.abilities.StaticAbility; import mage.abilities.StaticAbility;
import mage.abilities.costs.*; import mage.abilities.costs.Cost;
import mage.abilities.costs.OptionalAdditionalCost;
import mage.abilities.costs.OptionalAdditionalCostImpl;
import mage.abilities.costs.OptionalAdditionalModeSourceCosts;
import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.costs.mana.ManaCostsImpl;
import mage.constants.AbilityType;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.Zone; import mage.constants.Zone;
import mage.game.Game; import mage.game.Game;
import mage.players.Player; import mage.players.Player;
import java.util.Iterator; import java.util.HashSet;
import java.util.Set;
/** /**
* 702.40. Entwine * 702.40. Entwine
@ -24,20 +29,23 @@ import java.util.Iterator;
* 702.40b If the entwine cost was paid, follow the text of each of the modes in * 702.40b If the entwine cost was paid, follow the text of each of the modes in
* the order written on the card when the spell resolves. * the order written on the card when the spell resolves.
* *
* @author LevelX2 * @author JayDi85
*/ */
public class EntwineAbility extends StaticAbility implements OptionalAdditionalModeSourceCosts { public class EntwineAbility extends StaticAbility implements OptionalAdditionalModeSourceCosts {
private static final String keywordText = "Entwine"; private static final String keywordText = "Entwine";
protected static final String reminderText = "You may {cost} in addition to any other costs to use all modes.";
protected OptionalAdditionalCost additionalCost; protected OptionalAdditionalCost additionalCost;
protected Set<String> activations = new HashSet<>(); // same logic as KickerAbility: activations per zoneChangeCounter
public EntwineAbility(String manaString) { public EntwineAbility(String manaString) {
super(Zone.STACK, null); super(Zone.STACK, null);
this.additionalCost = new OptionalAdditionalCostImpl(keywordText, "Choose both if you pay the entwine cost.", new ManaCostsImpl(manaString)); this.additionalCost = new OptionalAdditionalCostImpl(keywordText, reminderText, new ManaCostsImpl(manaString));
} }
public EntwineAbility(Cost cost) { public EntwineAbility(Cost cost) {
this(cost, "Choose both if you pay the entwine cost."); this(cost, reminderText);
} }
public EntwineAbility(Cost cost, String reminderText) { public EntwineAbility(Cost cost, String reminderText) {
@ -48,7 +56,10 @@ public class EntwineAbility extends StaticAbility implements OptionalAdditionalM
public EntwineAbility(final EntwineAbility ability) { public EntwineAbility(final EntwineAbility ability) {
super(ability); super(ability);
additionalCost = ability.additionalCost; if (ability.additionalCost != null) {
this.additionalCost = ability.additionalCost.copy();
}
this.activations.addAll(ability.activations);
} }
@Override @Override
@ -57,61 +68,25 @@ public class EntwineAbility extends StaticAbility implements OptionalAdditionalM
} }
@Override @Override
public void addCost(Cost cost) { public void addOptionalAdditionalCosts(Ability ability, Game game) {
if (additionalCost != null) {
((Costs) additionalCost).add(cost);
}
}
@Override
public boolean isActivated() {
if (additionalCost != null) {
return additionalCost.isActivated();
}
return false;
}
public void resetCosts() {
if (additionalCost != null) {
additionalCost.reset();
}
}
@Override
public void changeModes(Ability ability, Game game) {
if (!(ability instanceof SpellAbility)) { if (!(ability instanceof SpellAbility)) {
return; return;
} }
Player player = game.getPlayer(ability.getControllerId()); Player player = game.getPlayer(ability.getControllerId());
if (player == null) { if (player == null) {
return; return;
} }
this.resetCosts();
this.resetCosts(game, ability);
if (additionalCost == null) { if (additionalCost == null) {
return; return;
} }
if (additionalCost.canPay(ability, ability.getSourceId(), ability.getControllerId(), game) if (additionalCost.canPay(ability, ability.getSourceId(), ability.getControllerId(), game)
&& player.chooseUse(Outcome.Benefit, "Pay " + additionalCost.getText(false) + " ?", ability, game)) { && player.chooseUse(Outcome.Benefit, "Pay " + additionalCost.getText(false) + " ?", ability, game)) {
addCostsToAbility(additionalCost, ability);
additionalCost.activate(); activateCost(game, ability);
int modeCount = ability.getModes().size();
ability.getModes().setAdditionalCost(this);
ability.getModes().setMinModes(modeCount);
ability.getModes().setMaxModes(modeCount);
}
}
@Override
public void addOptionalAdditionalModeCosts(Ability ability, Game game) {
if (additionalCost.isActivated()) {
for (Iterator it = ((Costs) additionalCost).iterator(); it.hasNext();) {
Cost cost = (Cost) it.next();
if (cost instanceof ManaCostsImpl) {
ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy());
} else {
ability.getCosts().add(cost.copy());
}
}
} }
} }
@ -134,11 +109,52 @@ public class EntwineAbility extends StaticAbility implements OptionalAdditionalM
} }
} }
public String getReminderText() { public void changeModes(Ability ability, Game game) {
if (additionalCost != null) { if (!costWasActivated(ability, game)) {
return additionalCost.getReminderText(); return;
} else {
return "";
} }
// activate max modes all the time
int maxModes = ability.getModes().size();
ability.getModes().setMinModes(maxModes);
ability.getModes().setMaxModes(maxModes);
}
private void addCostsToAbility(Cost cost, Ability ability) {
ability.addCost(cost.copy());
}
private void resetCosts(Game game, Ability source) {
if (additionalCost != null) {
additionalCost.reset();
}
String key = getActivationKey(source, game);
this.activations.remove(key);
}
private void activateCost(Game game, Ability source) {
String key = getActivationKey(source, game);
this.activations.add(key);
}
public boolean costWasActivated(Ability ability, Game game) {
String key = getActivationKey(ability, game);
return this.activations.contains(key);
}
private String getActivationKey(Ability source, Game game) {
// same logic as KickerAbility, uses for source ability only
int zcc = 0;
if (source.getAbilityType() == AbilityType.TRIGGERED) {
zcc = source.getSourceObjectZoneChangeCounter();
}
if (zcc == 0) {
zcc = game.getState().getZoneChangeCounter(source.getSourceId());
}
if (zcc > 0 && (source.getAbilityType() == AbilityType.TRIGGERED)) {
--zcc;
}
return String.valueOf(zcc);
} }
} }

View file

@ -82,7 +82,7 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo
public KickerAbility(final KickerAbility ability) { public KickerAbility(final KickerAbility ability) {
super(ability); super(ability);
for (OptionalAdditionalCost cost : ability.kickerCosts) { for (OptionalAdditionalCost cost : ability.kickerCosts) {
this.kickerCosts.add((OptionalAdditionalCost) cost.copy()); this.kickerCosts.add(cost.copy());
} }
this.keywordText = ability.keywordText; this.keywordText = ability.keywordText;
this.reminderText = ability.reminderText; this.reminderText = ability.reminderText;
@ -113,7 +113,7 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo
cost.reset(); cost.reset();
} }
String key = getActivationKey(source, "", game); String key = getActivationKey(source, "", game);
for (Iterator<String> iterator = activations.keySet().iterator(); iterator.hasNext();) { for (Iterator<String> iterator = activations.keySet().iterator(); iterator.hasNext(); ) {
String activationKey = iterator.next(); String activationKey = iterator.next();
if (activationKey.startsWith(key) if (activationKey.startsWith(key)
&& activations.get(activationKey) > 0) { && activations.get(activationKey) > 0) {
@ -192,15 +192,15 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo
// canPay checks only single mana available, not total mana usage // canPay checks only single mana available, not total mana usage
if (kickerCost.canPay(ability, sourceId, ability.getControllerId(), game) if (kickerCost.canPay(ability, sourceId, ability.getControllerId(), game)
&& player.chooseUse(/*Outcome.Benefit*/Outcome.AIDontUseIt, && player.chooseUse(/*Outcome.Benefit*/Outcome.AIDontUseIt,
"Pay " + times + kickerCost.getText(false) + " ?", ability, game)) { "Pay " + times + kickerCost.getText(false) + " ?", ability, game)) {
this.activateKicker(kickerCost, ability, game); this.activateKicker(kickerCost, ability, game);
if (kickerCost instanceof Costs) { if (kickerCost instanceof Costs) {
for (Iterator itKickerCost = ((Costs) kickerCost).iterator(); itKickerCost.hasNext();) { for (Iterator itKickerCost = ((Costs) kickerCost).iterator(); itKickerCost.hasNext(); ) {
Object kickerCostObject = itKickerCost.next(); Object kickerCostObject = itKickerCost.next();
if ((kickerCostObject instanceof Costs) if ((kickerCostObject instanceof Costs)
|| (kickerCostObject instanceof CostsImpl)) { || (kickerCostObject instanceof CostsImpl)) {
for (@SuppressWarnings("unchecked") Iterator<Cost> itDetails for (@SuppressWarnings("unchecked") Iterator<Cost> itDetails
= ((Costs) kickerCostObject).iterator(); itDetails.hasNext();) { = ((Costs) kickerCostObject).iterator(); itDetails.hasNext(); ) {
addKickerCostsToAbility(itDetails.next(), ability, game); addKickerCostsToAbility(itDetails.next(), ability, game);
} }
} else { } else {

View file

@ -0,0 +1,40 @@
package mage.abilities.keyword;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.OptionalAdditionalModeSourceCosts;
import mage.game.Game;
/**
* Same as KickerAbility, but can enable any number modes in spell ability
*
* @author JayDi85
*/
public class KickerWithAnyNumberModesAbility extends KickerAbility implements OptionalAdditionalModeSourceCosts {
public KickerWithAnyNumberModesAbility(Cost cost) {
super(cost);
}
public KickerWithAnyNumberModesAbility(final KickerWithAnyNumberModesAbility ability) {
super(ability);
}
@Override
public void changeModes(Ability ability, Game game) {
if (!isKicked(game, ability, "")) {
return;
}
// activate any number modes
int maxModes = ability.getModes().size();
ability.getModes().setMinModes(0);
ability.getModes().setMaxModes(maxModes);
}
@Override
public KickerWithAnyNumberModesAbility copy() {
return new KickerWithAnyNumberModesAbility(this);
}
}

View file

@ -2,6 +2,7 @@
package mage.abilities.keyword; package mage.abilities.keyword;
import mage.abilities.condition.common.KickedCondition;
import mage.abilities.costs.Cost; import mage.abilities.costs.Cost;
import mage.abilities.costs.OptionalAdditionalCost; import mage.abilities.costs.OptionalAdditionalCost;