mirror of
https://github.com/correl/mage.git
synced 2024-12-25 03:00:15 +00:00
* 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:
parent
cbb289337d
commit
653a2dd7b2
5 changed files with 109 additions and 40 deletions
|
@ -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()));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue