diff --git a/Mage.Sets/src/mage/cards/v/ViviOrnitier.java b/Mage.Sets/src/mage/cards/v/ViviOrnitier.java new file mode 100644 index 00000000000..cb0b408ee68 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/ViviOrnitier.java @@ -0,0 +1,85 @@ +package mage.cards.v; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.condition.common.MyTurnCondition; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.dynamicvalue.common.SourcePermanentPowerValue; +import mage.abilities.effects.common.DamagePlayersEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.effects.mana.AddManaInAnyCombinationEffect; +import mage.abilities.mana.ActivatedManaAbilityImpl; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class ViviOrnitier extends CardImpl { + + public ViviOrnitier(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(0); + this.toughness = new MageInt(3); + + // {0}: Add X mana in any combination of {U} and/or {R}, where X is Vivi Ornitier's power. Activate only during your turn and only once each turn. + this.addAbility(new ViviOrnitierManaAbility()); + + // Whenever you cast a noncreature spell, put a +1/+1 counter on Vivi Ornitier and it deals 1 damage to each opponent. + Ability ability = new SpellCastControllerTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), + StaticFilters.FILTER_SPELL_A_NON_CREATURE, false + ); + ability.addEffect(new DamagePlayersEffect(1, TargetController.OPPONENT, "it").concatBy("and")); + this.addAbility(ability); + } + + private ViviOrnitier(final ViviOrnitier card) { + super(card); + } + + @Override + public ViviOrnitier copy() { + return new ViviOrnitier(this); + } +} + +class ViviOrnitierManaAbility extends ActivatedManaAbilityImpl { + + ViviOrnitierManaAbility() { + super( + Zone.BATTLEFIELD, + new AddManaInAnyCombinationEffect( + SourcePermanentPowerValue.NOT_NEGATIVE, + SourcePermanentPowerValue.NOT_NEGATIVE, + ColoredManaSymbol.U, + ColoredManaSymbol.R + ), + new GenericManaCost(0) + ); + this.condition = MyTurnCondition.instance; + this.maxActivationsPerTurn = 1; + } + + private ViviOrnitierManaAbility(final ViviOrnitierManaAbility ability) { + super(ability); + } + + public ViviOrnitierManaAbility copy() { + return new ViviOrnitierManaAbility(this); + } + + @Override + public String getRule() { + return super.getRule() + " Activate only during your turn and only once each turn."; + } +} diff --git a/Mage.Sets/src/mage/sets/FinalFantasy.java b/Mage.Sets/src/mage/sets/FinalFantasy.java index 72f4d2fd161..ab3ec845269 100644 --- a/Mage.Sets/src/mage/sets/FinalFantasy.java +++ b/Mage.Sets/src/mage/sets/FinalFantasy.java @@ -299,6 +299,9 @@ public final class FinalFantasy extends ExpansionSet { cards.add(new SetCardInfo("Vincent Valentine", 383, Rarity.RARE, mage.cards.v.VincentValentine.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Vincent Valentine", 454, Rarity.RARE, mage.cards.v.VincentValentine.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Vincent Valentine", 528, Rarity.RARE, mage.cards.v.VincentValentine.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vivi Ornitier", 248, Rarity.MYTHIC, mage.cards.v.ViviOrnitier.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vivi Ornitier", 321, Rarity.MYTHIC, mage.cards.v.ViviOrnitier.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Vivi Ornitier", 514, Rarity.MYTHIC, mage.cards.v.ViviOrnitier.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Warrior's Sword", 169, Rarity.COMMON, mage.cards.w.WarriorsSword.class)); cards.add(new SetCardInfo("Wastes", 309, Rarity.COMMON, mage.cards.w.Wastes.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("White Auracite", 41, Rarity.COMMON, mage.cards.w.WhiteAuracite.class, NON_FULL_USE_VARIOUS)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/mana/LimitPerTurnTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/mana/LimitPerTurnTest.java new file mode 100644 index 00000000000..1b2e090c09e --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/mana/LimitPerTurnTest.java @@ -0,0 +1,72 @@ +package org.mage.test.cards.mana; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class LimitPerTurnTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.s.ShireScarecrow Shire Scarecrow} {2} + * Artifact Creature — Scarecrow + * Defender + * {1}: Add one mana of any color. Activate only once each turn. + * 0/3 + */ + private static final String scarecrow = "Shire Scarecrow"; + + + @Test + public void test_LimitOncePerTurn() { + String vanguard = "Elite Vanguard"; // {W} 2/1 + String goblin = "Raging Goblin"; // {R} 1/1 + addCard(Zone.BATTLEFIELD, playerA, scarecrow, 1); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + addCard(Zone.HAND, playerA, vanguard, 1); + addCard(Zone.HAND, playerA, goblin, 1); + + checkPlayableAbility("1: Vanguard can be cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + vanguard, true); + checkPlayableAbility("1: goblin can be cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + goblin, true); + + setChoice(playerA, "White"); // choice for Scarecrow mana + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, vanguard); + + checkPlayableAbility("2: goblin can not be cast", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast " + goblin, false); + + checkPlayableAbility("3: goblin can be cast on turn 3", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + goblin, true); + + setChoice(playerA, "Red"); // choice for Scarecrow mana + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, goblin); + + setStopAt(3, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertPermanentCount(playerA, 5); + } + + @Test + public void test_MultipleScarecrows() { + String deus = "Deus of Calamity"; // {R/G}{R/G}{R/G}{R/G}{R/G} + String boggart = "Boggart Ram-Gang"; // {R/G}{R/G}{R/G} + addCard(Zone.BATTLEFIELD, playerA, scarecrow, 3); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5); + addCard(Zone.HAND, playerA, deus, 1); + addCard(Zone.HAND, playerA, boggart, 1); + + checkPlayableAbility("1: boggart can be cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + boggart, true); + checkPlayableAbility("1: deus can not be cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + deus, false); + + setChoice(playerA, "Red"); // choice for Scarecrow mana + setChoice(playerA, "Red"); // choice for Scarecrow mana + setChoice(playerA, "Green"); // choice for Scarecrow mana + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, boggart); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertPermanentCount(playerA, boggart, 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/fin/ViviOrnitierTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/fin/ViviOrnitierTest.java new file mode 100644 index 00000000000..96cebf3da81 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/fin/ViviOrnitierTest.java @@ -0,0 +1,65 @@ +package org.mage.test.cards.single.fin; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class ViviOrnitierTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.v.ViviOrnitier Vivi Ornitier} {1}{U}{R} + * Legendary Creature — Wizard + * {0}: Add X mana in any combination of {U} and/or {R}, where X is Vivi Ornitier’s power. Activate only during your turn and only once each turn. + * Whenever you cast a noncreature spell, put a +1/+1 counter on Vivi Ornitier and it deals 1 damage to each opponent. + * 0/3 + */ + private static final String vivi = "Vivi Ornitier"; + + /** + * Creatures you control get +2/+2. + */ + private static final String dictate = "Dictate of Heliod"; + + private static final String bolt = "Lightning Bolt"; + private static final String incinerate = "Incinerate"; + + @Test + public void test_NoPower() { + addCard(Zone.BATTLEFIELD, playerA, vivi, 1); + addCard(Zone.HAND, playerA, bolt); + + checkPlayableAbility("bolt can not be cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + bolt, false); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + setStrictChooseMode(true); + execute(); + } + + @Test + public void test_2Power() { + addCard(Zone.BATTLEFIELD, playerA, vivi, 1); + addCard(Zone.BATTLEFIELD, playerA, dictate, 1); + addCard(Zone.HAND, playerA, bolt); + addCard(Zone.HAND, playerA, incinerate); + + checkPlayableAbility("1: bolt can be cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + bolt, true); + checkPlayableAbility("1: incinerate can be cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + incinerate, true); + + setChoice(playerA, "X=0"); // choose {U} color distribution for vivi on 2 power + setChoice(playerA, "X=2"); // choose {R} color distribution for vivi on 2 power + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, incinerate, playerB); + + checkPlayableAbility("2: bolt can not be cast", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast " + bolt, false); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertLife(playerB, 20 - 3 - 1); + assertPowerToughness(playerA, vivi, 3, 6); + } +} diff --git a/Mage/src/main/java/mage/abilities/ActivatedAbility.java b/Mage/src/main/java/mage/abilities/ActivatedAbility.java index 4cf270557ac..02205c61626 100644 --- a/Mage/src/main/java/mage/abilities/ActivatedAbility.java +++ b/Mage/src/main/java/mage/abilities/ActivatedAbility.java @@ -99,6 +99,11 @@ public interface ActivatedAbility extends Ability { int getMaxActivationsPerTurn(Game game); + /** + * how many more time can this be activated this turn? + */ + int getMaxMoreActivationsThisTurn(Game game); + ActivatedAbility setTiming(TimingRule timing); ActivatedAbility setCondition(Condition condition); diff --git a/Mage/src/main/java/mage/abilities/ActivatedAbilityImpl.java b/Mage/src/main/java/mage/abilities/ActivatedAbilityImpl.java index 60c803102ee..0290cb0b6e1 100644 --- a/Mage/src/main/java/mage/abilities/ActivatedAbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/ActivatedAbilityImpl.java @@ -215,6 +215,23 @@ public abstract class ActivatedAbilityImpl extends AbilityImpl implements Activa || activationInfo.activationCounter < getMaxActivationsPerTurn(game); } + public int getMaxMoreActivationsThisTurn(Game game) { + if (getMaxActivationsPerTurn(game) == Integer.MAX_VALUE && maxActivationsPerGame == Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + ActivationInfo activationInfo = getActivationInfo(game); + if (activationInfo == null) { + return Math.min(maxActivationsPerGame, getMaxActivationsPerTurn(game)); + } + if (activationInfo.totalActivations >= maxActivationsPerGame) { + return 0; + } + if (activationInfo.turnNum != game.getTurnNum()) { + return getMaxActivationsPerTurn(game); + } + return Math.max(0, getMaxActivationsPerTurn(game) - activationInfo.activationCounter); + } + @Override public boolean activate(Game game, Set allowedIdentifiers, boolean noMana) { if (!hasMoreActivationsThisTurn(game) || !super.activate(game, allowedIdentifiers, noMana)) { diff --git a/Mage/src/main/java/mage/abilities/mana/ManaAbility.java b/Mage/src/main/java/mage/abilities/mana/ManaAbility.java index 94e7a221a42..38a567fabe9 100644 --- a/Mage/src/main/java/mage/abilities/mana/ManaAbility.java +++ b/Mage/src/main/java/mage/abilities/mana/ManaAbility.java @@ -1,13 +1,13 @@ package mage.abilities.mana; -import java.util.List; -import java.util.Set; import mage.Mana; import mage.constants.ManaType; import mage.game.Game; +import java.util.List; +import java.util.Set; + /** - * * @author LevelX2 */ public interface ManaAbility { @@ -20,7 +20,7 @@ public interface ManaAbility { * @return */ List getNetMana(Game game); - + /** * Used to check the possible mana production to determine which spells * and/or abilities can be used. (player.getPlayable()). @@ -28,7 +28,7 @@ public interface ManaAbility { * * @param game * @param possibleManaInPool The possible mana already produced by other sources for this calculation option - * @return + * @return */ List getNetMana(Game game, Mana possibleManaInPool); @@ -60,7 +60,12 @@ public interface ManaAbility { * @return */ boolean isPoolDependant(); - + + /** + * How many more times can this ability be activated this turn + */ + int getMaxMoreActivationsThisTurn(Game game); + ManaAbility setPoolDependant(boolean pooleDependant); - + } diff --git a/Mage/src/main/java/mage/abilities/mana/ManaOptions.java b/Mage/src/main/java/mage/abilities/mana/ManaOptions.java index 02a214d1d55..000e610435e 100644 --- a/Mage/src/main/java/mage/abilities/mana/ManaOptions.java +++ b/Mage/src/main/java/mage/abilities/mana/ManaOptions.java @@ -393,6 +393,7 @@ public class ManaOptions extends LinkedHashSet { && onlyManaCosts && manaToAdd.countColored() > 0; boolean canHaveBetterValues; + int maxRepeat = manaAbility.getMaxMoreActivationsThisTurn(game); Mana possibleMana = new Mana(); Mana improvedMana = new Mana(); @@ -401,9 +402,11 @@ public class ManaOptions extends LinkedHashSet { // example: {G}: Add one mana of any color for (Mana possiblePay : ManaOptions.getPossiblePayCombinations(cost, startingMana)) { improvedMana.setToMana(startingMana); + int currentAttempt = 0; do { // loop until all mana replaced by better values canHaveBetterValues = false; + currentAttempt++; // it's impossible to analyse all payment order (pay {R} for {1}, {Any} for {G}, etc) // so use simple cost simulation by subtract @@ -441,7 +444,7 @@ public class ManaOptions extends LinkedHashSet { } improvedMana.setToMana(possibleMana); } - } while (repeatable && canHaveBetterValues && improvedMana.includesMana(possiblePay)); + } while (repeatable && (currentAttempt < maxRepeat) && canHaveBetterValues && improvedMana.includesMana(possiblePay)); } return oldManaWasReplaced; } @@ -670,12 +673,14 @@ final class Comparators { for (T first : elements) { for (T second : elements) { int firstGreaterThanSecond = comparator.compare(first, second); - if (firstGreaterThanSecond <= 0) + if (firstGreaterThanSecond <= 0) { continue; + } for (T third : elements) { int secondGreaterThanThird = comparator.compare(second, third); - if (secondGreaterThanThird <= 0) + if (secondGreaterThanThird <= 0) { continue; + } int firstGreaterThanThird = comparator.compare(first, third); if (firstGreaterThanThird <= 0) { // Uncomment the following line to step through the failed case diff --git a/Mage/src/main/java/mage/abilities/mana/TriggeredManaAbility.java b/Mage/src/main/java/mage/abilities/mana/TriggeredManaAbility.java index ca425dbfbcc..827c853f713 100644 --- a/Mage/src/main/java/mage/abilities/mana/TriggeredManaAbility.java +++ b/Mage/src/main/java/mage/abilities/mana/TriggeredManaAbility.java @@ -104,6 +104,11 @@ public abstract class TriggeredManaAbility extends TriggeredAbilityImpl implemen return poolDependant; } + @Override + public int getMaxMoreActivationsThisTurn(Game game) { + return getRemainingTriggersLimitEachTurn(game); + } + @Override public TriggeredManaAbility setPoolDependant(boolean poolDependant) { this.poolDependant = poolDependant;