mirror of
https://github.com/magefree/mage.git
synced 2025-12-21 19:11:59 -08:00
implement [FIN] Vivi Ornitier ; limit mana computation for "only once per turn" abilities (#13639)
fixes #10930
This commit is contained in:
parent
fa20361e2e
commit
a9af84f533
9 changed files with 272 additions and 10 deletions
85
Mage.Sets/src/mage/cards/v/ViviOrnitier.java
Normal file
85
Mage.Sets/src/mage/cards/v/ViviOrnitier.java
Normal file
|
|
@ -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.";
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<MageIdentifier> allowedIdentifiers, boolean noMana) {
|
||||
if (!hasMoreActivationsThisTurn(game) || !super.activate(game, allowedIdentifiers, noMana)) {
|
||||
|
|
|
|||
|
|
@ -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<Mana> 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<Mana> 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);
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -393,6 +393,7 @@ public class ManaOptions extends LinkedHashSet<Mana> {
|
|||
&& 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<Mana> {
|
|||
// 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<Mana> {
|
|||
}
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue