* Doubling Cube - Added support for possible mana calculation (related to #6698).

This commit is contained in:
LevelX2 2020-08-18 00:22:53 +02:00
parent 27db13605e
commit c48331f216
15 changed files with 379 additions and 79 deletions

View file

@ -1,5 +1,7 @@
package mage.cards.d; package mage.cards.d;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID; import java.util.UUID;
import mage.ConditionalMana; import mage.ConditionalMana;
import mage.Mana; import mage.Mana;
@ -25,7 +27,8 @@ public final class DoublingCube extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}");
// {3}, {T}: Double the amount of each type of mana in your mana pool. // {3}, {T}: Double the amount of each type of mana in your mana pool.
Ability ability = new SimpleManaAbility(Zone.BATTLEFIELD, new DoublingCubeEffect(), new ManaCostsImpl("{3}")); Ability ability = new SimpleManaAbility(Zone.BATTLEFIELD, new DoublingCubeEffect(), new ManaCostsImpl("{3}"))
.setPoolDependant(true);
ability.addCost(new TapSourceCost()); ability.addCost(new TapSourceCost());
this.addAbility(ability); this.addAbility(ability);
@ -52,6 +55,22 @@ class DoublingCubeEffect extends ManaEffect {
super(effect); super(effect);
} }
@Override
public List<Mana> getNetMana(Game game, Mana possibleManaInPool, Ability source) {
List<Mana> netMana = new ArrayList<>();
netMana.add(new Mana( // remove possible mana conditions
possibleManaInPool.getRed(),
possibleManaInPool.getGreen(),
possibleManaInPool.getBlue(),
possibleManaInPool.getWhite(),
possibleManaInPool.getBlack(),
0, // Generic may not be included
possibleManaInPool.getAny(),
possibleManaInPool.getColorless())
);
return netMana;
}
@Override @Override
public Mana produceMana(Game game, Ability source) { public Mana produceMana(Game game, Ability source) {
if (game != null) { if (game != null) {

View file

@ -22,10 +22,10 @@ public final class FetidHeath extends CardImpl {
public FetidHeath (UUID ownerId, CardSetInfo setInfo) { public FetidHeath (UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.LAND},null); super(ownerId,setInfo,new CardType[]{CardType.LAND},null);
// {tap}: Add {C}. // {T}: Add {C}.
this.addAbility(new ColorlessManaAbility()); this.addAbility(new ColorlessManaAbility());
// {W/B}, {tap}: Add {W}{W}, {W}{B}, or {B}{B}. // {W/B}, {T}: Add {W}{W}, {W}{B}, or {B}{B}.
SimpleManaAbility ability = new SimpleManaAbility(Zone.BATTLEFIELD, Mana.WhiteMana(2), new ManaCostsImpl("{W/B}")); SimpleManaAbility ability = new SimpleManaAbility(Zone.BATTLEFIELD, Mana.WhiteMana(2), new ManaCostsImpl("{W/B}"));
ability.addCost(new TapSourceCost()); ability.addCost(new TapSourceCost());
this.addAbility(ability); this.addAbility(ability);

View file

@ -21,7 +21,7 @@ public class CastDestroySpellsTest extends CardTestPlayerBaseAI {
addCard(Zone.HAND, playerA, "Orzhov Charm"); // {W}{B} addCard(Zone.HAND, playerA, "Orzhov Charm"); // {W}{B}
// {T}: Add {C}. // {T}: Add {C}.
// {T} {W/B}, {T}: Add {W}{W}, {W}{B}, or {B}{B}. // {W/B}, {T}: Add {W}{W}, {W}{B}, or {B}{B}.
addCard(Zone.BATTLEFIELD, playerA, "Fetid Heath", 1); addCard(Zone.BATTLEFIELD, playerA, "Fetid Heath", 1);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); addCard(Zone.BATTLEFIELD, playerA, "Plains", 1);

View file

@ -1,10 +1,14 @@
package org.mage.test.cards.mana; package org.mage.test.cards.mana;
import mage.abilities.mana.ManaOptions;
import mage.constants.ManaType; import mage.constants.ManaType;
import mage.constants.PhaseStep; import mage.constants.PhaseStep;
import mage.constants.Zone; import mage.constants.Zone;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase; import org.mage.test.serverside.base.CardTestPlayerBase;
import static org.mage.test.utils.ManaOptionsTestUtils.assertManaOptions;
public class DoublingCubeTest extends CardTestPlayerBase { public class DoublingCubeTest extends CardTestPlayerBase {
@ -17,7 +21,7 @@ public class DoublingCubeTest extends CardTestPlayerBase {
//issue 3443 //issue 3443
@Test @Test
public void DoublingCubeEldraziTemple() { public void test_DoublingCubeEldraziTemple() {
addCard(Zone.BATTLEFIELD, playerA, temple); addCard(Zone.BATTLEFIELD, playerA, temple);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); addCard(Zone.BATTLEFIELD, playerA, "Forest", 3);
@ -37,4 +41,105 @@ public class DoublingCubeTest extends CardTestPlayerBase {
assertManaPool(playerA, ManaType.COLORLESS, 4); assertManaPool(playerA, ManaType.COLORLESS, 4);
assertAllCommandsUsed(); assertAllCommandsUsed();
} }
@Test
public void test_AvailableMana() {
setStrictChooseMode(true);
// {3}, {T}: Double the amount of each type of mana in your mana pool.
addCard(Zone.BATTLEFIELD, playerA, cube);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 4);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4);
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
execute();
assertAllCommandsUsed();
ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame);
Assert.assertEquals("mana variations don't fit", 4, manaOptions.size());
assertManaOptions("{R}{R}{R}{R}{R}{R}{R}{R}{G}{G}", manaOptions);
assertManaOptions("{R}{R}{R}{R}{R}{R}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{R}{R}{R}{R}{G}{G}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{R}{R}{G}{G}{G}{G}{G}{G}{G}{G}", manaOptions);
}
@Test
public void test_AvailableMana2() {
setStrictChooseMode(true);
// {3}, {T}: Double the amount of each type of mana in your mana pool.
addCard(Zone.BATTLEFIELD, playerA, cube, 2);
// {T}: Add Colorless.
// {1}, {T}: Add Black.
// {2}, {T}: Add Blue or Red.
addCard(Zone.BATTLEFIELD, playerA, "Castle Sengir", 2);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 4);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4);
setStopAt(1, PhaseStep.UPKEEP);
execute();
assertAllCommandsUsed();
ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame);
Assert.assertEquals("mana variations don't fit", 138, manaOptions.size());
assertManaOptions("{C}{C}{C}{C}{C}{C}{C}{C}{R}{R}{R}{R}{R}{R}{R}{R}{R}{R}{R}{R}{R}{R}", manaOptions);
assertManaOptions("{C}{C}{C}{C}{C}{C}{R}{R}{R}{R}{R}{R}{R}{R}{R}{R}{R}{R}{R}{R}{R}{R}", manaOptions);
assertManaOptions("{C}{C}{C}{C}{C}{C}{C}{C}{R}{R}{R}{R}{R}{R}{R}{R}{R}{R}{R}{R}{G}{G}", manaOptions);
assertManaOptions("{C}{C}{C}{C}{C}{C}{C}{C}{R}{R}{R}{R}{R}{R}{R}{R}{R}{R}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{C}{C}{C}{C}{C}{C}{C}{C}{R}{R}{R}{R}{R}{R}{R}{R}{G}{G}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{C}{C}{C}{C}{C}{C}{C}{C}{R}{R}{R}{R}{R}{R}{G}{G}{G}{G}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{C}{C}{C}{C}{C}{C}{C}{C}{R}{R}{R}{R}{G}{G}{G}{G}{G}{G}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{C}{C}{C}{C}{C}{C}{C}{C}{R}{R}{G}{G}{G}{G}{G}{G}{G}{G}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{C}{C}{C}{C}{C}{C}{C}{C}{G}{G}{G}{G}{G}{G}{G}{G}{G}{G}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{C}{C}{R}{R}{R}{R}{R}{R}{R}{R}{R}{R}{G}{G}{G}{G}{G}{G}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{C}{C}{C}{C}{B}{B}{R}{R}{G}{G}{G}{G}{G}{G}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{C}{C}{C}{C}{B}{B}{R}{R}{R}{R}{R}{R}{R}{R}{R}{R}{R}{R}", manaOptions);
assertManaOptions("{C}{C}{C}{C}{B}{B}{B}{B}{R}{R}{R}{R}{R}{R}{R}{R}{R}{R}", manaOptions);
assertManaOptions("{C}{C}{C}{C}{B}{B}{R}{R}{R}{R}{R}{R}{R}{R}{R}{R}{G}{G}", manaOptions);
assertManaOptions("{C}{C}{C}{C}{B}{B}{R}{R}{R}{R}{R}{R}{R}{R}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{C}{C}{C}{C}{B}{B}{B}{B}{R}{R}{R}{R}{R}{R}{R}{R}{G}{G}", manaOptions);
assertManaOptions("{C}{C}{C}{C}{B}{B}{B}{B}{R}{R}{R}{R}{R}{R}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{C}{C}{C}{C}{B}{B}{R}{R}{R}{R}{R}{R}{G}{G}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{C}{C}{C}{C}{B}{B}{R}{R}{R}{R}{G}{G}{G}{G}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{C}{C}{C}{C}{B}{B}{B}{B}{R}{R}{R}{R}{G}{G}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{C}{C}{C}{C}{B}{B}{B}{B}{R}{R}{G}{G}{G}{G}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{C}{C}{C}{C}{B}{B}{G}{G}{G}{G}{G}{G}{G}{G}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{C}{C}{C}{C}{B}{B}{B}{B}{G}{G}{G}{G}{G}{G}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{C}{C}{C}{C}{U}{U}{R}{R}{R}{R}{R}{R}{R}{R}", manaOptions);
assertManaOptions("{C}{C}{C}{C}{U}{U}{U}{U}{R}{R}{R}{R}{R}{R}", manaOptions);
assertManaOptions("{C}{C}{C}{C}{U}{U}{R}{R}{R}{R}{R}{R}{G}{G}", manaOptions);
assertManaOptions("{C}{C}{C}{C}{U}{U}{R}{R}{R}{R}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{C}{C}{C}{C}{U}{U}{U}{U}{R}{R}{R}{R}{G}{G}", manaOptions);
assertManaOptions("{C}{C}{C}{C}{U}{U}{U}{U}{R}{R}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{C}{C}{C}{C}{U}{U}{R}{R}{G}{G}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{C}{C}{C}{C}{U}{U}{G}{G}{G}{G}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{C}{C}{C}{C}{U}{U}{U}{U}{G}{G}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{B}{B}{B}{B}{B}{B}{R}{R}{R}{R}{R}{R}{R}{R}", manaOptions);
assertManaOptions("{B}{B}{B}{B}{B}{B}{B}{B}{R}{R}{R}{R}{R}{R}", manaOptions);
assertManaOptions("{B}{B}{B}{B}{B}{B}{R}{R}{R}{R}{R}{R}{G}{G}", manaOptions);
assertManaOptions("{B}{B}{B}{B}{B}{B}{R}{R}{R}{R}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{B}{B}{B}{B}{B}{B}{B}{B}{R}{R}{R}{R}{G}{G}", manaOptions);
assertManaOptions("{B}{B}{B}{B}{B}{B}{B}{B}{R}{R}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{B}{B}{B}{B}{B}{B}{R}{R}{G}{G}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{B}{B}{B}{B}{B}{B}{G}{G}{G}{G}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{B}{B}{B}{B}{B}{B}{B}{B}{G}{G}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{U}{U}{B}{B}{R}{R}{R}{R}{R}{R}", manaOptions);
assertManaOptions("{U}{U}{U}{U}{B}{B}{R}{R}{R}{R}", manaOptions);
assertManaOptions("{U}{U}{B}{B}{B}{B}{R}{R}{R}{R}", manaOptions);
assertManaOptions("{U}{U}{U}{U}{B}{B}{B}{B}{R}{R}", manaOptions);
assertManaOptions("{U}{U}{B}{B}{R}{R}{R}{R}{G}{G}", manaOptions);
assertManaOptions("{U}{U}{B}{B}{R}{R}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{U}{U}{U}{U}{B}{B}{R}{R}{G}{G}", manaOptions);
assertManaOptions("{U}{U}{U}{U}{B}{B}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{U}{U}{B}{B}{B}{B}{R}{R}{G}{G}", manaOptions);
assertManaOptions("{U}{U}{B}{B}{B}{B}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{U}{U}{U}{U}{B}{B}{B}{B}{G}{G}", manaOptions);
assertManaOptions("{U}{U}{B}{B}{G}{G}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{U}{U}{U}{U}{U}{U}", manaOptions);
}
} }

View file

@ -168,7 +168,7 @@ public class TappedForManaRelatedTest extends CardTestPlayerBase {
addCard(Zone.BATTLEFIELD, playerA, "Castle Sengir", 1); addCard(Zone.BATTLEFIELD, playerA, "Castle Sengir", 1);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
setStopAt(1, PhaseStep.BEGIN_COMBAT); setStopAt(1, PhaseStep.UPKEEP);
execute(); execute();
assertAllCommandsUsed(); assertAllCommandsUsed();

View file

@ -5,7 +5,6 @@ import mage.constants.PhaseStep;
import mage.constants.Zone; import mage.constants.Zone;
import mage.counters.CounterType; import mage.counters.CounterType;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase; import org.mage.test.serverside.base.CardTestPlayerBase;
import static org.mage.test.utils.ManaOptionsTestUtils.*; import static org.mage.test.utils.ManaOptionsTestUtils.*;
@ -330,6 +329,8 @@ public class ManaOptionsTest extends CardTestPlayerBase {
@Test @Test
public void testFetidHeath() { public void testFetidHeath() {
// {T}: Add {C}.
// {W/B}, {T}: Add {W}{W}, {W}{B}, or {B}{B}.
addCard(Zone.BATTLEFIELD, playerA, "Fetid Heath", 1); addCard(Zone.BATTLEFIELD, playerA, "Fetid Heath", 1);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); addCard(Zone.BATTLEFIELD, playerA, "Plains", 1);

View file

@ -267,6 +267,12 @@ public interface Abilities<T extends Ability> extends List<T>, Serializable {
*/ */
boolean containsClass(Class classObject); boolean containsClass(Class classObject);
/**
* Returns true if one or more of the abilities are activated mana abilities with the pollDependant flag set to true.
* @return
*/
boolean hasPoolDependantAbilities();
/** /**
* Copies this set of abilities. This copy should be new instances of all * Copies this set of abilities. This copy should be new instances of all
* the contained abilities. * the contained abilities.

View file

@ -12,6 +12,7 @@ import org.apache.log4j.Logger;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import mage.abilities.mana.ManaAbility;
/** /**
* @param <T> * @param <T>
@ -185,6 +186,13 @@ public class AbilitiesImpl<T extends Ability> extends ArrayList<T> implements Ab
return zonedAbilities; return zonedAbilities;
} }
@Override
public boolean hasPoolDependantAbilities() {
return stream()
.anyMatch(ability -> ability.getAbilityType() == AbilityType.MANA
&& ((ManaAbility) ability).isPoolDependant());
}
@Override @Override
public Abilities<ProtectionAbility> getProtectionAbilities() { public Abilities<ProtectionAbility> getProtectionAbilities() {
return stream() return stream()
@ -224,7 +232,7 @@ public class AbilitiesImpl<T extends Ability> extends ArrayList<T> implements Ab
@Override @Override
public boolean contains(T ability) { public boolean contains(T ability) {
for (Iterator<T> iterator = this.iterator(); iterator.hasNext(); ) { // simple loop can cause java.util.ConcurrentModificationException for (Iterator<T> iterator = this.iterator(); iterator.hasNext();) { // simple loop can cause java.util.ConcurrentModificationException
T test = iterator.next(); T test = iterator.next();
// Checking also by getRule() without other restrictions is a problem when a triggered ability will be copied to a permanent that had the same ability // Checking also by getRule() without other restrictions is a problem when a triggered ability will be copied to a permanent that had the same ability
// already before the copy. Because then it keeps the triggered ability twice and it triggers twice. // already before the copy. Because then it keeps the triggered ability twice and it triggers twice.

View file

@ -80,6 +80,22 @@ public abstract class ManaEffect extends OneShotEffect {
return netMana; return netMana;
} }
/**
* Returns the currently available max mana variations the effect can
* produce. Also provides the possible before produced mana from other
* abilities. Needed for some abilities that produce mana related to the
* mana existing in the mana pool.
*
* @param game
* @param possibleManaInPool The possible mana already produced by other
* sources for this calculation option
* @param source
* @return
*/
public List<Mana> getNetMana(Game game, Mana possibleManaInPool, Ability source) {
return getNetMana(game, source);
}
/** /**
* The type of mana a permanent "could produce" is the type of mana that any * The type of mana a permanent "could produce" is the type of mana that any
* ability of that permanent can generate, taking into account any * ability of that permanent can generate, taking into account any
@ -95,7 +111,6 @@ public abstract class ManaEffect extends OneShotEffect {
return ManaType.getManaTypesFromManaList(getNetMana(game, source)); return ManaType.getManaTypesFromManaList(getNetMana(game, source));
} }
/** /**
* Produced the mana the effect can produce (DO NOT add it to mana pool -- * Produced the mana the effect can produce (DO NOT add it to mana pool --
* return all added as mana object to process by replace events) * return all added as mana object to process by replace events)

View file

@ -19,6 +19,7 @@ public abstract class ActivatedManaAbilityImpl extends ActivatedAbilityImpl impl
protected List<Mana> netMana = new ArrayList<>(); protected List<Mana> netMana = new ArrayList<>();
protected boolean undoPossible; protected boolean undoPossible;
protected boolean poolDependant;
public ActivatedManaAbilityImpl(Zone zone, ManaEffect effect, Cost cost) { public ActivatedManaAbilityImpl(Zone zone, ManaEffect effect, Cost cost) {
super(AbilityType.MANA, zone); super(AbilityType.MANA, zone);
@ -36,6 +37,7 @@ public abstract class ActivatedManaAbilityImpl extends ActivatedAbilityImpl impl
super(ability); super(ability);
this.netMana.addAll(ability.netMana); this.netMana.addAll(ability.netMana);
this.undoPossible = ability.undoPossible; this.undoPossible = ability.undoPossible;
this.poolDependant = ability.poolDependant;
} }
@Override @Override
@ -101,6 +103,23 @@ public abstract class ActivatedManaAbilityImpl extends ActivatedAbilityImpl impl
return netManaCopy; return netManaCopy;
} }
@Override
public List<Mana> getNetMana(Game game, Mana possibleManaInPool) {
if (isPoolDependant()) {
List<Mana> poolDependantNetMana = new ArrayList<>();
for (Effect effect : getEffects()) {
if (effect instanceof ManaEffect) {
List<Mana> effectNetMana = ((ManaEffect) effect).getNetMana(game, possibleManaInPool, this);
if (effectNetMana != null) {
poolDependantNetMana.addAll(effectNetMana);
}
}
}
return poolDependantNetMana;
}
return getNetMana(game);
}
@Override @Override
public Set<ManaType> getProducableManaTypes(Game game) { public Set<ManaType> getProducableManaTypes(Game game) {
Set<ManaType> manaTypes = new HashSet<>(); Set<ManaType> manaTypes = new HashSet<>();
@ -127,8 +146,9 @@ public abstract class ActivatedManaAbilityImpl extends ActivatedAbilityImpl impl
* game revealing information is related (like reveal the top card of the * game revealing information is related (like reveal the top card of the
* library) * library)
* <p> * <p>
* TODO: it helps with single mana activate for mana pool, but will not work while activates on paying for casting * TODO: it helps with single mana activate for mana pool, but will not work
* (e.g. user can cheats to see next draw card) * while activates on paying for casting (e.g. user can cheats to see next
* draw card)
* *
* @return * @return
*/ */
@ -140,4 +160,15 @@ public abstract class ActivatedManaAbilityImpl extends ActivatedAbilityImpl impl
this.undoPossible = undoPossible; this.undoPossible = undoPossible;
} }
@Override
public boolean isPoolDependant() {
return poolDependant;
}
@Override
public ActivatedManaAbilityImpl setPoolDependant(boolean poolDependant) {
this.poolDependant = poolDependant;
return this;
}
} }

View file

@ -8,6 +8,7 @@ package mage.abilities.mana;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import mage.Mana; import mage.Mana;
import mage.abilities.Ability;
import mage.constants.ManaType; import mage.constants.ManaType;
import mage.game.Game; import mage.game.Game;
@ -26,6 +27,17 @@ public interface ManaAbility {
*/ */
List<Mana> getNetMana(Game game); List<Mana> getNetMana(Game game);
/**
* Used to check the possible mana production to determine which spells
* and/or abilities can be used. (player.getPlayable()).
* Only used for abilities were the poolDependant flag is set
*
* @param game
* @param possibleManaInPool The possible mana already produced by other sources for this calculation option
* @return
*/
List<Mana> getNetMana(Game game, Mana possibleManaInPool);
/** /**
* The type of mana a permanent "could produce" is the type of mana that any * The type of mana a permanent "could produce" is the type of mana that any
* ability of that permanent can generate, taking into account any * ability of that permanent can generate, taking into account any
@ -45,4 +57,16 @@ public interface ManaAbility {
* @return * @return
*/ */
boolean definesMana(Game game); boolean definesMana(Game game);
/**
* Set to true if the ability is dependant from the mana pool. E.g. the more
* mana the pool contains the more mana the ability can produce (Doubling
* Cube). Therefore the use of that ability after other mana abilities does produce more mana.
*
* @return
*/
boolean isPoolDependant();
ManaAbility setPoolDependant(boolean pooleDependant);
} }

View file

@ -176,6 +176,7 @@ public class ManaOptions extends ArrayList<Mana> {
newMana.add(mana); newMana.add(mana);
newMana.add(triggeredManaVariation); newMana.add(triggeredManaVariation);
this.add(newMana); this.add(newMana);
wasUsable = true;
} }
} }
} }
@ -190,7 +191,7 @@ public class ManaOptions extends ArrayList<Mana> {
Mana startingMana = prevMana.copy(); Mana startingMana = prevMana.copy();
Mana manaCosts = ability.getManaCosts().getMana(); Mana manaCosts = ability.getManaCosts().getMana();
if (startingMana.includesMana(manaCosts)) { // can pay the mana costs to use the ability if (startingMana.includesMana(manaCosts)) { // can pay the mana costs to use the ability
if (!subtractCostAddMana(manaCosts, triggeredManaVariation, ability.getCosts().isEmpty(), startingMana)) { if (!subtractCostAddMana(manaCosts, triggeredManaVariation, ability.getCosts().isEmpty(), startingMana, ability, game)) {
// the starting mana includes mana parts that the increased mana does not include, so add starting mana also as an option // the starting mana includes mana parts that the increased mana does not include, so add starting mana also as an option
add(prevMana); add(prevMana);
} }
@ -219,6 +220,7 @@ public class ManaOptions extends ArrayList<Mana> {
newMana.add(mana); newMana.add(mana);
newMana.add(triggeredManaVariation); newMana.add(triggeredManaVariation);
this.add(newMana); this.add(newMana);
wasUsable = true;
} }
} }
} }
@ -229,21 +231,8 @@ public class ManaOptions extends ArrayList<Mana> {
for (Mana previousMana : copy) { for (Mana previousMana : copy) {
CombineWithExisting: CombineWithExisting:
for (Mana manaOption : ability.getManaCosts().getManaOptions()) { for (Mana manaOption : ability.getManaCosts().getManaOptions()) {
Mana newMana = new Mana(previousMana);
if (previousMana.includesMana(manaOption)) { // costs can be paid if (previousMana.includesMana(manaOption)) { // costs can be paid
newMana.subtractCost(manaOption); wasUsable |= subtractCostAddMana(manaOption, triggeredManaVariation, ability.getCosts().isEmpty(), previousMana, ability, game);
newMana.add(triggeredManaVariation);
// if the new mana is in all colors more than another already existing than replace
for (Mana existingMana : this) {
Mana moreValuable = Mana.getMoreValuableMana(newMana, existingMana);
if (moreValuable != null) {
existingMana.setToMana(moreValuable);
replaces++;
continue CombineWithExisting;
}
}
// no existing Mana includes this new mana so add
this.add(newMana);
} }
} }
} }
@ -264,6 +253,35 @@ public class ManaOptions extends ArrayList<Mana> {
return wasUsable; return wasUsable;
} }
public boolean addManaPoolDependant(List<ActivatedManaAbilityImpl> abilities, Game game) {
boolean wasUsable = false;
if (!abilities.isEmpty()) {
if (abilities.size() == 1) {
ActivatedManaAbilityImpl ability = (ActivatedManaAbilityImpl) abilities.get(0);
List<Mana> copy = copy();
this.clear();
for (Mana previousMana : copy) {
Mana startingMana = previousMana.copy();
Mana manaCosts = ability.getManaCosts().getMana();
if (startingMana.includesMana(manaCosts)) { // can pay the mana costs to use the ability
for (Mana manaOption : ability.getManaCosts().getManaOptions()) {
if (!subtractCostAddMana(manaOption, null, ability.getCosts().isEmpty(), startingMana, ability, game)) {
// the starting mana includes mana parts that the increased mana does not include, so add starting mana also as an option
add(previousMana);
}
}
wasUsable = true;
} else {
// mana costs can't be paid so keep starting mana
add(previousMana);
}
}
}
}
return wasUsable;
}
public static List<Mana> getTriggeredManaVariations(Game game, Ability ability, Mana baseMana) { public static List<Mana> getTriggeredManaVariations(Game game, Ability ability, Mana baseMana) {
List<Mana> baseManaPlusTriggeredMana = new ArrayList<>(); List<Mana> baseManaPlusTriggeredMana = new ArrayList<>();
baseManaPlusTriggeredMana.add(baseMana); baseManaPlusTriggeredMana.add(baseMana);
@ -391,62 +409,60 @@ public class ManaOptions extends ArrayList<Mana> {
* @param oldManaWasReplaced returns the info if the new complete mana does * @param oldManaWasReplaced returns the info if the new complete mana does
* replace the current mana completely * replace the current mana completely
*/ */
private boolean subtractCostAddMana(Mana cost, Mana manaToAdd, boolean onlyManaCosts, Mana currentMana) { private boolean subtractCostAddMana(Mana cost, Mana manaToAdd, boolean onlyManaCosts, Mana currentMana, ManaAbility manaAbility, Game game) {
boolean oldManaWasReplaced = false; // true if the newly created mana includes all mana possibilities of the old boolean oldManaWasReplaced = false; // true if the newly created mana includes all mana possibilities of the old
boolean repeatable = false; boolean repeatable = false;
if ((manaToAdd.countColored() > 0 || manaToAdd.getAny() > 0) && manaToAdd.count() > 0 && onlyManaCosts) { if (manaToAdd != null && (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 repeatable = true; // only replace to any with mana costs only will be repeated if able
} }
Mana prevMana = currentMana.copy();
// 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())) {
for (Mana payCombination : ManaOptions.getPossiblePayCombinations(cost.getGeneric(), currentMana)) {
Mana currentManaCopy = currentMana.copy();
while (currentManaCopy.includesMana(payCombination)) { // loop for multiple usage if possible
boolean newCombinations = false;
for (Mana payCombination : ManaOptions.getPossiblePayCombinations(cost, currentMana)) {
Mana currentManaCopy = currentMana.copy(); // copy start mana because in iteration it will be updated
while (currentManaCopy.includesMana(payCombination)) { // loop for multiple usage if possible
boolean newCombinations = false;
if (manaToAdd == null) {
Mana newMana = currentManaCopy.copy();
newMana.subtract(payCombination);
for (Mana mana : manaAbility.getNetMana(game, newMana)) { // get the mana to add from the ability related to the currently generated possible mana pool
newMana.add(mana);
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
Mana moreValuable = Mana.getMoreValuableMana(currentManaCopy, newMana);
if (newMana.equals(moreValuable)) {
oldManaWasReplaced = true; // the new mana includes all possibilities of the old one, so no need to add it after return
if (!currentMana.equalManaValue(currentManaCopy)) {
this.removeEqualMana(currentManaCopy);
}
}
currentManaCopy = newMana.copy();
}
}
} else {
Mana newMana = currentManaCopy.copy(); Mana newMana = currentManaCopy.copy();
newMana.subtract(payCombination); newMana.subtract(payCombination);
newMana.add(manaToAdd); newMana.add(manaToAdd);
// Mana moreValuable = Mana.getMoreValuableMana(currentMana, newMana);
if (!isExistingManaCombination(newMana)) { if (!isExistingManaCombination(newMana)) {
this.add(newMana); // add the new combination this.add(newMana); // add the new combination
newCombinations = true; // repeat the while as long there are new combinations and usage is repeatable newCombinations = true; // repeat the while as long there are new combinations and usage is repeatable
currentManaCopy = newMana.copy(); Mana moreValuable = Mana.getMoreValuableMana(currentManaCopy, newMana);
Mana moreValuable = Mana.getMoreValuableMana(currentMana, newMana); if (newMana.equals(moreValuable)) {
if (!oldManaWasReplaced && newMana.equals(moreValuable)) { oldManaWasReplaced = true; // the new mana includes all possible mana of the old one, so no need to add it after return
oldManaWasReplaced = true; // the new mana includes all possibilities of the old one, so no need to add it after return if (!currentMana.equalManaValue(currentManaCopy)) {
this.removeEqualMana(currentManaCopy);
}
} }
} currentManaCopy = newMana.copy();
if (!newCombinations || !repeatable) {
break;
} }
} }
if (!newCombinations || !repeatable) {
} break;
} else {
while (currentMana.includesMana(cost)) { // loop for multiple usage if possible
currentMana.subtractCost(cost);
currentMana.add(manaToAdd);
if (!repeatable) {
break; // Stop adding multiple usages of the ability
}
}
// Don't use mana that only reduce the available mana
if (prevMana.contains(currentMana) && prevMana.count() > currentMana.count()) {
currentMana.setToMana(prevMana);
}
Mana moreValuable = Mana.getMoreValuableMana(prevMana, currentMana);
if (!prevMana.equals(moreValuable)) {
this.add(currentMana);
if (moreValuable != null) {
oldManaWasReplaced = true; // the new mana includes all possibilities of the old one
} }
} }
} }
forceManaDeduplication(); forceManaDeduplication();
return oldManaWasReplaced; return oldManaWasReplaced;
@ -457,12 +473,23 @@ public class ManaOptions extends ArrayList<Mana> {
* @param manaAvailable * @param manaAvailable
* @return * @return
*/ */
public static List<Mana> getPossiblePayCombinations(int number, Mana manaAvailable) { public static List<Mana> getPossiblePayCombinations(Mana manaCost, Mana manaAvailable) {
List<Mana> payCombinations = new ArrayList<>(); List<Mana> payCombinations = new ArrayList<>();
List<String> payCombinationsStrings = new ArrayList<>(); List<String> payCombinationsStrings = new ArrayList<>();
// handle fixed mana costs
Mana fixedMana = manaCost.copy();
if (manaCost.getGeneric() == 0) {
payCombinations.add(fixedMana);
return payCombinations;
}
fixedMana.setGeneric(0);
Mana manaAfterFixedPayment = manaAvailable.copy();
manaAfterFixedPayment.subtract(fixedMana);
// handle generic mana costs
if (manaAvailable.countColored() > 0) { if (manaAvailable.countColored() > 0) {
for (int i = 0; i < number; i++) { for (int i = 0; i < manaCost.getGeneric(); i++) {
List<Mana> existingManas = new ArrayList<>(); List<Mana> existingManas = new ArrayList<>();
if (i > 0) { if (i > 0) {
existingManas.addAll(payCombinations); existingManas.addAll(payCombinations);
@ -472,7 +499,7 @@ public class ManaOptions extends ArrayList<Mana> {
existingManas.add(new Mana()); existingManas.add(new Mana());
} }
for (Mana existingMana : existingManas) { for (Mana existingMana : existingManas) {
Mana manaToPayFrom = manaAvailable.copy(); Mana manaToPayFrom = manaAfterFixedPayment.copy();
manaToPayFrom.subtract(existingMana); manaToPayFrom.subtract(existingMana);
if (manaToPayFrom.getBlack() > 0 && !payCombinationsStrings.contains(existingMana.toString() + Mana.BlackMana(1).toString())) { if (manaToPayFrom.getBlack() > 0 && !payCombinationsStrings.contains(existingMana.toString() + Mana.BlackMana(1).toString())) {
manaToPayFrom.subtract(Mana.BlackMana(1)); manaToPayFrom.subtract(Mana.BlackMana(1));
@ -494,6 +521,10 @@ public class ManaOptions extends ArrayList<Mana> {
manaToPayFrom.subtract(Mana.WhiteMana(1)); manaToPayFrom.subtract(Mana.WhiteMana(1));
ManaOptions.addManaCombination(Mana.WhiteMana(1), existingMana, payCombinations, payCombinationsStrings); ManaOptions.addManaCombination(Mana.WhiteMana(1), existingMana, payCombinations, payCombinationsStrings);
} }
if (manaToPayFrom.getColorless() > 0 && !payCombinationsStrings.contains(existingMana.toString() + Mana.ColorlessMana(1).toString())) {
manaToPayFrom.subtract(Mana.ColorlessMana(1));
ManaOptions.addManaCombination(Mana.ColorlessMana(1), existingMana, payCombinations, payCombinationsStrings);
}
// Pay with any only needed if colored payment was not possible // Pay with any only needed if colored payment was not possible
if (payCombinations.isEmpty() && manaToPayFrom.getAny() > 0 && !payCombinationsStrings.contains(existingMana.toString() + Mana.AnyMana(1).toString())) { if (payCombinations.isEmpty() && manaToPayFrom.getAny() > 0 && !payCombinationsStrings.contains(existingMana.toString() + Mana.AnyMana(1).toString())) {
manaToPayFrom.subtract(Mana.AnyMana(1)); manaToPayFrom.subtract(Mana.AnyMana(1));
@ -502,7 +533,10 @@ public class ManaOptions extends ArrayList<Mana> {
} }
} }
} else { } else {
payCombinations.add(Mana.ColorlessMana(number)); payCombinations.add(Mana.ColorlessMana(manaCost.getGeneric()));
}
for (Mana mana : payCombinations) {
mana.add(fixedMana);
} }
return payCombinations; return payCombinations;
} }
@ -517,6 +551,18 @@ public class ManaOptions extends ArrayList<Mana> {
return false; return false;
} }
public boolean removeEqualMana(Mana manaToRemove) {
boolean result = false;
for (Iterator<Mana> iterator = this.iterator(); iterator.hasNext();) {
Mana next = iterator.next();
if (next.equalManaValue(manaToRemove)) {
iterator.remove();
result = true;
}
}
return result;
}
public static void addManaCombination(Mana mana, Mana existingMana, List<Mana> payCombinations, List<String> payCombinationsStrings) { public static void addManaCombination(Mana mana, Mana existingMana, List<Mana> payCombinations, List<String> payCombinationsStrings) {
Mana newMana = existingMana.copy(); Mana newMana = existingMana.copy();
newMana.add(mana); newMana.add(mana);
@ -572,6 +618,7 @@ public class ManaOptions extends ArrayList<Mana> {
return false; return false;
} }
@Override
public String toString() { public String toString() {
Iterator<Mana> it = this.iterator(); Iterator<Mana> it = this.iterator();
if (!it.hasNext()) { if (!it.hasNext()) {

View file

@ -72,4 +72,5 @@ public class SimpleManaAbility extends ActivatedManaAbilityImpl {
return new ArrayList<>(netMana); return new ArrayList<>(netMana);
} }
} }

View file

@ -22,6 +22,7 @@ import mage.constants.ManaType;
public abstract class TriggeredManaAbility extends TriggeredAbilityImpl implements ManaAbility { public abstract class TriggeredManaAbility extends TriggeredAbilityImpl implements ManaAbility {
protected List<Mana> netMana = new ArrayList<>(); protected List<Mana> netMana = new ArrayList<>();
protected boolean poolDependant;
public TriggeredManaAbility(Zone zone, ManaEffect effect) { public TriggeredManaAbility(Zone zone, ManaEffect effect) {
this(zone, effect, false); this(zone, effect, false);
@ -37,6 +38,7 @@ public abstract class TriggeredManaAbility extends TriggeredAbilityImpl implemen
public TriggeredManaAbility(final TriggeredManaAbility ability) { public TriggeredManaAbility(final TriggeredManaAbility ability) {
super(ability); super(ability);
this.netMana.addAll(ability.netMana); this.netMana.addAll(ability.netMana);
this.poolDependant = ability.poolDependant;
} }
/** /**
@ -60,6 +62,23 @@ public abstract class TriggeredManaAbility extends TriggeredAbilityImpl implemen
return new ArrayList<>(netMana); return new ArrayList<>(netMana);
} }
@Override
public List<Mana> getNetMana(Game game, Mana possibleManaInPool) {
if (isPoolDependant()) {
List<Mana> poolDependantNetMana = new ArrayList<>();
for (Effect effect : getEffects()) {
if (effect instanceof ManaEffect) {
List<Mana> effectNetMana = ((ManaEffect) effect).getNetMana(game, possibleManaInPool, this);
if (effectNetMana != null) {
poolDependantNetMana.addAll(effectNetMana);
}
}
}
return poolDependantNetMana;
}
return getNetMana(game);
}
@Override @Override
public Set<ManaType> getProducableManaTypes(Game game) { public Set<ManaType> getProducableManaTypes(Game game) {
Set<ManaType> manaTypes = new HashSet<>(); Set<ManaType> manaTypes = new HashSet<>();
@ -80,4 +99,16 @@ public abstract class TriggeredManaAbility extends TriggeredAbilityImpl implemen
public boolean definesMana(Game game) { public boolean definesMana(Game game) {
return !netMana.isEmpty(); return !netMana.isEmpty();
} }
@Override
public boolean isPoolDependant() {
return poolDependant;
}
@Override
public TriggeredManaAbility setPoolDependant(boolean poolDependant) {
this.poolDependant = poolDependant;
return this;
}
} }

View file

@ -2894,7 +2894,7 @@ public abstract class PlayerImpl implements Player, Serializable {
for (Permanent permanent : game.getBattlefield().getActivePermanents(playerId, game)) { // Some permanents allow use of abilities from non controlling players. so check all permanents in range for (Permanent permanent : game.getBattlefield().getActivePermanents(playerId, game)) { // Some permanents allow use of abilities from non controlling players. so check all permanents in range
Boolean canUse = null; Boolean canUse = null;
boolean canAdd = false; boolean canAdd = false;
boolean withCost = false; boolean useLater = false; // sources with mana costs or mana pool dependency
Abilities<ActivatedManaAbilityImpl> manaAbilities Abilities<ActivatedManaAbilityImpl> manaAbilities
= permanent.getAbilities().getAvailableActivatedManaAbilities(Zone.BATTLEFIELD, playerId, game); // returns ability only if canActivate is true = permanent.getAbilities().getAvailableActivatedManaAbilities(Zone.BATTLEFIELD, playerId, game); // returns ability only if canActivate is true
for (Iterator<ActivatedManaAbilityImpl> it = manaAbilities.iterator(); it.hasNext();) { for (Iterator<ActivatedManaAbilityImpl> it = manaAbilities.iterator(); it.hasNext();) {
@ -2907,7 +2907,7 @@ public abstract class PlayerImpl implements Player, Serializable {
if (!ability.hasTapCost()) { if (!ability.hasTapCost()) {
it.remove(); it.remove();
Abilities<ActivatedManaAbilityImpl> noTapAbilities = new AbilitiesImpl<>(ability); Abilities<ActivatedManaAbilityImpl> noTapAbilities = new AbilitiesImpl<>(ability);
if (ability.getManaCosts().isEmpty()) { if (ability.getManaCosts().isEmpty() && !ability.isPoolDependant()) {
sourceWithoutManaCosts.add(noTapAbilities); sourceWithoutManaCosts.add(noTapAbilities);
} else { } else {
sourceWithCosts.add(noTapAbilities); sourceWithCosts.add(noTapAbilities);
@ -2916,14 +2916,14 @@ public abstract class PlayerImpl implements Player, Serializable {
} }
canAdd = true; canAdd = true;
if (!ability.getManaCosts().isEmpty()) { if (!ability.getManaCosts().isEmpty() || ability.isPoolDependant()) {
withCost = true; useLater = true;
break; break;
} }
} }
} }
if (canAdd) { if (canAdd) {
if (withCost) { if (useLater) {
sourceWithCosts.add(manaAbilities); sourceWithCosts.add(manaAbilities);
} else { } else {
sourceWithoutManaCosts.add(manaAbilities); sourceWithoutManaCosts.add(manaAbilities);
@ -2936,17 +2936,29 @@ public abstract class PlayerImpl implements Player, Serializable {
} }
boolean anAbilityWasUsed = true; boolean anAbilityWasUsed = true;
boolean usePoolDependantAbilities = false; // use such abilities later than other if possible because it can maximize mana production
while (anAbilityWasUsed && !sourceWithCosts.isEmpty()) { while (anAbilityWasUsed && !sourceWithCosts.isEmpty()) {
anAbilityWasUsed = false; anAbilityWasUsed = false;
for (Iterator<Abilities<ActivatedManaAbilityImpl>> iterator = sourceWithCosts.iterator(); iterator.hasNext();) { for (Iterator<Abilities<ActivatedManaAbilityImpl>> iterator = sourceWithCosts.iterator(); iterator.hasNext();) {
Abilities<ActivatedManaAbilityImpl> manaAbilities = iterator.next(); Abilities<ActivatedManaAbilityImpl> manaAbilities = iterator.next();
boolean used = availableMana.addManaWithCost(manaAbilities, game); if (usePoolDependantAbilities || !manaAbilities.hasPoolDependantAbilities()) {
if (used) { boolean used;
iterator.remove(); if (manaAbilities.hasPoolDependantAbilities()) {
availableMana.removeDuplicated(); used = availableMana.addManaPoolDependant(manaAbilities, game);
anAbilityWasUsed = true; } else {
used = availableMana.addManaWithCost(manaAbilities, game);
}
if (used) {
iterator.remove();
availableMana.removeDuplicated();
anAbilityWasUsed = true;
}
} }
} }
if (!anAbilityWasUsed && !usePoolDependantAbilities) {
usePoolDependantAbilities = true;
anAbilityWasUsed = true;
}
} }
// remove duplicated variants (see ManaOptionsTest for info - when that rises) // remove duplicated variants (see ManaOptionsTest for info - when that rises)