* Available mana calculation - Fixed a problem with repeatable mana converting abilities (e.g. Farrelite Priest) that were only considered once (#6698).

This commit is contained in:
LevelX2 2020-07-24 12:06:14 +02:00
parent cbb289337d
commit 653a2dd7b2
5 changed files with 109 additions and 40 deletions

View file

@ -1,4 +1,3 @@
package mage.cards.e;
import java.util.UUID;
@ -18,11 +17,11 @@ import mage.constants.Zone;
public final class EyeOfRamos extends CardImpl {
public EyeOfRamos(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{3}");
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}");
// {tap}: Add {U}.
// {T}: Add {U}.
this.addAbility(new BlueManaAbility());
// Sacrifice Eye of Ramos: Add {U}.
this.addAbility(new SimpleManaAbility(Zone.BATTLEFIELD, Mana.BlueMana(1), new SacrificeSourceCost()));
}

View file

@ -240,4 +240,42 @@ public class TappedForManaRelatedTest extends CardTestPlayerBase {
Assert.assertEquals("mana variations don't fit", 1, manaOptions.size());
assertManaOptions("{Any}", manaOptions);
}
@Test
public void TestEyeOfRamos() {
setStrictChooseMode(true);
// {T}: Add {U}.
// Sacrifice Eye of Ramos: Add {U}.
addCard(Zone.BATTLEFIELD, playerA, "Eye of Ramos", 2);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame);
Assert.assertEquals("mana variations don't fit", 1, manaOptions.size());
assertManaOptions("{U}{U}{U}{U}", manaOptions);
}
@Test
public void TestFarrelitePriest() {
setStrictChooseMode(true);
// {1}: Add {W}. If this ability has been activated four or more times this turn, sacrifice Farrelite Priest at the beginning of the next end step.
addCard(Zone.BATTLEFIELD, playerA, "Farrelite Priest", 1);
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame);
Assert.assertEquals("mana variations don't fit", 5, manaOptions.size());
assertManaOptions("{W}{W}{W}{W}", manaOptions);
assertManaOptions("{W}{W}{W}{B}", manaOptions);
assertManaOptions("{W}{W}{B}{B}", manaOptions);
assertManaOptions("{W}{B}{B}{B}", manaOptions);
assertManaOptions("{B}{B}{B}{B}", manaOptions);
}
}

View file

@ -1,5 +1,9 @@
package mage.abilities;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.costs.*;
import mage.abilities.costs.common.PayLifeCost;
@ -8,6 +12,7 @@ import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.Effect;
import mage.abilities.effects.Effects;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.ManaEffect;
import mage.abilities.hint.Hint;
import mage.abilities.mana.ActivatedManaAbilityImpl;
import mage.cards.Card;
@ -30,12 +35,6 @@ import mage.util.ThreadLocalStringBuilder;
import mage.watchers.Watcher;
import org.apache.log4j.Logger;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import mage.abilities.effects.common.ManaEffect;
/**
* @author BetaSteward_at_googlemail.com
*/
@ -54,7 +53,7 @@ public abstract class AbilityImpl implements Ability {
protected ManaCosts<ManaCost> manaCostsToPay;
protected Costs<Cost> costs;
protected Costs<Cost> optionalCosts;
protected Modes modes; // access to it by GetModes only (it's can be override by some abilities)
protected Modes modes; // access to it by GetModes only (it can be overridden by some abilities)
protected Zone zone;
protected String name;
protected AbilityWord abilityWord;
@ -65,7 +64,7 @@ public abstract class AbilityImpl implements Ability {
protected boolean activated = false;
protected boolean worksFaceDown = false;
protected int sourceObjectZoneChangeCounter;
protected List<Watcher> watchers = new ArrayList<>(); // access to it by GetWatchers only (it's can be override by some abilities)
protected List<Watcher> watchers = new ArrayList<>(); // access to it by GetWatchers only (it can be overridden by some abilities)
protected List<Ability> subAbilities = null;
protected boolean canFizzle = true;
protected TargetAdjuster targetAdjuster = null;
@ -1243,10 +1242,13 @@ public abstract class AbilityImpl implements Ability {
}
/**
* Dynamic cost modification for ability.
* Example: if it need stack related info (like real targets) then must check two states (game.inCheckPlayableState):
* 1. In playable state it must check all possible use cases (e.g. allow to reduce on any available target and modes)
* 2. In real cast state it must check current use case (e.g. real selected targets and modes)
* Dynamic cost modification for ability.<br>
* Example: if it need stack related info (like real targets) then must
* check two states (game.inCheckPlayableState): <br>
* 1. In playable state it must check all possible use cases (e.g. allow to
* reduce on any available target and modes) <br>
* 2. In real cast state it must check current use case (e.g. real selected
* targets and modes)
*
* @param costAdjuster
*/

View file

@ -348,26 +348,34 @@ public class ManaOptions extends ArrayList<Mana> {
private boolean subtractCostAddMana(Mana cost, Mana manaToAdd, boolean onlyManaCosts, Mana currentMana) {
boolean oldManaWasReplaced = false; // true if the newly created mana includes all mana possibilities of the old
boolean repeatable = false;
if (manaToAdd.getAny() == 1 && manaToAdd.count() == 1 && onlyManaCosts) {
if ((manaToAdd.countColored() > 0 || manaToAdd.getAny() > 0) && manaToAdd.count() > 0 && onlyManaCosts) {
// deactivated because it does cause loops TODO: Find reason
repeatable = true; // only replace to any with mana costs only will be repeated if able
}
Mana prevMana = currentMana.copy();
if (currentMana.includesMana(cost)) { // it can be paid
if (currentMana.includesMana(cost)) { // cost can be paid
// generic mana costs can be paid with different colored mana, can lead to different color combinations
if (cost.getGeneric() > 0 && cost.getGeneric() > (currentMana.getGeneric() + currentMana.getColorless())) {
Mana coloredCost = cost.copy();
coloredCost.setGeneric(0);
currentMana.subtract(coloredCost);
for (Mana payCombination : getPossiblePayCombinations(cost.getGeneric(), currentMana)) {
Mana newMana = currentMana.copy();
newMana.subtract(payCombination);
newMana.add(manaToAdd);
Mana moreValuable = Mana.getMoreValuableMana(prevMana, newMana);
if (!prevMana.equals(moreValuable)) {
this.add(newMana);
if (moreValuable != null) {
oldManaWasReplaced = true; // the new mana includes all possibilities of the old one
Mana currentManaCopy = currentMana.copy();
while (currentManaCopy.includesMana(payCombination)) { // loop for multiple usage if possible
boolean newCombinations = false;
Mana newMana = currentManaCopy.copy();
newMana.subtract(payCombination);
newMana.add(manaToAdd);
// Mana moreValuable = Mana.getMoreValuableMana(currentMana, newMana);
if (!isExistingManaCombination(newMana)) {
this.add(newMana); // add the new combination
newCombinations = true; // repeat the while as long there are new combinations and usage is repeatable
currentManaCopy = newMana.copy();
Mana moreValuable = Mana.getMoreValuableMana(currentMana, newMana);
if (!oldManaWasReplaced && newMana.equals(moreValuable)) {
oldManaWasReplaced = true; // the new mana includes all possibilities of the old one, so no need to add it after return
}
}
if (!newCombinations || !repeatable) {
break;
}
}
@ -447,6 +455,16 @@ public class ManaOptions extends ArrayList<Mana> {
return payCombinations;
}
private boolean isExistingManaCombination(Mana newMana) {
for (Mana mana : this) {
Mana moreValuable = Mana.getMoreValuableMana(mana, newMana);
if (mana.equals(moreValuable)) {
return true;
}
}
return false;
}
private void addManaCombination(Mana mana, Mana existingMana, List<Mana> payCombinations, List<String> payCombinationsStrings) {
Mana newMana = existingMana.copy();
newMana.add(mana);
@ -478,4 +496,20 @@ public class ManaOptions extends ArrayList<Mana> {
}
}
}
/**
* Checks if the given mana (cost) is already included in one available mana
* option
*
* @param mana
* @return
*/
public boolean enough(Mana mana) {
for (Mana avail : this) {
if (mana.enough(avail)) {
return true;
}
}
return false;
}
}

View file

@ -3157,10 +3157,8 @@ public abstract class PlayerImpl implements Player, Serializable {
game.getContinuousEffects().costModification(copyAbility, game);
for (Mana mana : copyAbility.getManaCostsToPay().getOptions()) {
for (Mana avail : availableMana) {
if (mana.enough(avail)) {
return true;
}
if (availableMana.enough(mana)) {
return true;
}
}
}
@ -3196,10 +3194,8 @@ public abstract class PlayerImpl implements Player, Serializable {
game.getContinuousEffects().costModification(copyAbility, game);
for (Mana mana : copyAbility.getManaCostsToPay().getOptions()) {
for (Mana avail : availableMana) {
if (mana.enough(avail)) {
return true;
}
if (availableMana.enough(mana)) {
return true;
}
}
}
@ -3247,7 +3243,7 @@ public abstract class PlayerImpl implements Player, Serializable {
protected ActivatedAbility findActivatedAbilityFromAlternativeSourceCost(MageObject object, ManaOptions availableMana, Ability ability, Game game) {
// return play ability that can activate AlternativeSourceCosts
if (ability instanceof AlternativeSourceCosts && !(object instanceof Permanent)) {
if (ability instanceof AlternativeSourceCosts && object != null && !(object instanceof Permanent)) {
ActivatedAbility playAbility = null;
if (object.isLand()) {
playAbility = (PlayLandAbility) CardUtil.getAbilities(object, game).stream().filter(a -> a instanceof PlayLandAbility).findFirst().orElse(null);
@ -3263,8 +3259,8 @@ public abstract class PlayerImpl implements Player, Serializable {
// Even mana cost can't be checked here without lookahead
// So make it available all the time
boolean canUse;
if (ability instanceof MorphAbility) {
canUse = game.canPlaySorcery(playerId) && ((MorphAbility) ability).isAvailable(playAbility, game);
if (ability instanceof MorphAbility && object instanceof Card && game.canPlaySorcery(getId())) {
canUse = canPlayCardByAlternateCost((Card) object, availableMana, ability, game);
} else {
canUse = canPlay(playAbility, availableMana, object, game); // canPlay already checks alternative source costs and all conditions
}