From c48331f21630ca446f263c3307c43a919b9cb100 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Tue, 18 Aug 2020 00:22:53 +0200 Subject: [PATCH] * Doubling Cube - Added support for possible mana calculation (related to #6698). --- Mage.Sets/src/mage/cards/d/DoublingCube.java | 21 ++- Mage.Sets/src/mage/cards/f/FetidHeath.java | 4 +- .../test/AI/basic/CastDestroySpellsTest.java | 2 +- .../test/cards/mana/DoublingCubeTest.java | 107 +++++++++++- .../cards/mana/TappedForManaRelatedTest.java | 2 +- .../org/mage/test/utils/ManaOptionsTest.java | 3 +- .../main/java/mage/abilities/Abilities.java | 6 + .../java/mage/abilities/AbilitiesImpl.java | 10 +- .../abilities/effects/mana/ManaEffect.java | 17 +- .../mana/ActivatedManaAbilityImpl.java | 35 +++- .../java/mage/abilities/mana/ManaAbility.java | 24 +++ .../java/mage/abilities/mana/ManaOptions.java | 163 +++++++++++------- .../abilities/mana/SimpleManaAbility.java | 1 + .../abilities/mana/TriggeredManaAbility.java | 31 ++++ .../main/java/mage/players/PlayerImpl.java | 32 ++-- 15 files changed, 379 insertions(+), 79 deletions(-) diff --git a/Mage.Sets/src/mage/cards/d/DoublingCube.java b/Mage.Sets/src/mage/cards/d/DoublingCube.java index d8990180bb2..12618afe938 100644 --- a/Mage.Sets/src/mage/cards/d/DoublingCube.java +++ b/Mage.Sets/src/mage/cards/d/DoublingCube.java @@ -1,5 +1,7 @@ package mage.cards.d; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; import mage.ConditionalMana; import mage.Mana; @@ -25,7 +27,8 @@ public final class DoublingCube extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); // {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()); this.addAbility(ability); @@ -52,6 +55,22 @@ class DoublingCubeEffect extends ManaEffect { super(effect); } + @Override + public List getNetMana(Game game, Mana possibleManaInPool, Ability source) { + List 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 public Mana produceMana(Game game, Ability source) { if (game != null) { diff --git a/Mage.Sets/src/mage/cards/f/FetidHeath.java b/Mage.Sets/src/mage/cards/f/FetidHeath.java index fcf372fdb5e..4833f452da0 100644 --- a/Mage.Sets/src/mage/cards/f/FetidHeath.java +++ b/Mage.Sets/src/mage/cards/f/FetidHeath.java @@ -22,10 +22,10 @@ public final class FetidHeath extends CardImpl { public FetidHeath (UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.LAND},null); - // {tap}: Add {C}. + // {T}: Add {C}. 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}")); ability.addCost(new TapSourceCost()); this.addAbility(ability); diff --git a/Mage.Tests/src/test/java/org/mage/test/AI/basic/CastDestroySpellsTest.java b/Mage.Tests/src/test/java/org/mage/test/AI/basic/CastDestroySpellsTest.java index 7089a560abd..f7d0c366f5d 100644 --- a/Mage.Tests/src/test/java/org/mage/test/AI/basic/CastDestroySpellsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/AI/basic/CastDestroySpellsTest.java @@ -21,7 +21,7 @@ public class CastDestroySpellsTest extends CardTestPlayerBaseAI { addCard(Zone.HAND, playerA, "Orzhov Charm"); // {W}{B} // {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, "Plains", 1); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/mana/DoublingCubeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/mana/DoublingCubeTest.java index 69744bf9437..186da50bd68 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/mana/DoublingCubeTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/mana/DoublingCubeTest.java @@ -1,10 +1,14 @@ package org.mage.test.cards.mana; +import mage.abilities.mana.ManaOptions; import mage.constants.ManaType; import mage.constants.PhaseStep; import mage.constants.Zone; +import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; +import static org.mage.test.utils.ManaOptionsTestUtils.assertManaOptions; public class DoublingCubeTest extends CardTestPlayerBase { @@ -17,7 +21,7 @@ public class DoublingCubeTest extends CardTestPlayerBase { //issue 3443 @Test - public void DoublingCubeEldraziTemple() { + public void test_DoublingCubeEldraziTemple() { addCard(Zone.BATTLEFIELD, playerA, temple); addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); @@ -37,4 +41,105 @@ public class DoublingCubeTest extends CardTestPlayerBase { assertManaPool(playerA, ManaType.COLORLESS, 4); 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); + } + } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/mana/TappedForManaRelatedTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/mana/TappedForManaRelatedTest.java index 6e941e49d2e..5df540ecf53 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/mana/TappedForManaRelatedTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/mana/TappedForManaRelatedTest.java @@ -168,7 +168,7 @@ public class TappedForManaRelatedTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Castle Sengir", 1); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); - setStopAt(1, PhaseStep.BEGIN_COMBAT); + setStopAt(1, PhaseStep.UPKEEP); execute(); assertAllCommandsUsed(); diff --git a/Mage.Tests/src/test/java/org/mage/test/utils/ManaOptionsTest.java b/Mage.Tests/src/test/java/org/mage/test/utils/ManaOptionsTest.java index 632e2b4d263..d610538fd3e 100644 --- a/Mage.Tests/src/test/java/org/mage/test/utils/ManaOptionsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/utils/ManaOptionsTest.java @@ -5,7 +5,6 @@ import mage.constants.PhaseStep; import mage.constants.Zone; import mage.counters.CounterType; import org.junit.Assert; -import org.junit.Ignore; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; import static org.mage.test.utils.ManaOptionsTestUtils.*; @@ -330,6 +329,8 @@ public class ManaOptionsTest extends CardTestPlayerBase { @Test 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, "Plains", 1); diff --git a/Mage/src/main/java/mage/abilities/Abilities.java b/Mage/src/main/java/mage/abilities/Abilities.java index 1c5054ab1a2..56133529adf 100644 --- a/Mage/src/main/java/mage/abilities/Abilities.java +++ b/Mage/src/main/java/mage/abilities/Abilities.java @@ -267,6 +267,12 @@ public interface Abilities extends List, Serializable { */ 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 * the contained abilities. diff --git a/Mage/src/main/java/mage/abilities/AbilitiesImpl.java b/Mage/src/main/java/mage/abilities/AbilitiesImpl.java index 93de7e94498..d3a78f2d7cf 100644 --- a/Mage/src/main/java/mage/abilities/AbilitiesImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilitiesImpl.java @@ -12,6 +12,7 @@ import org.apache.log4j.Logger; import java.util.*; import java.util.stream.Collectors; +import mage.abilities.mana.ManaAbility; /** * @param @@ -185,6 +186,13 @@ public class AbilitiesImpl extends ArrayList implements Ab return zonedAbilities; } + @Override + public boolean hasPoolDependantAbilities() { + return stream() + .anyMatch(ability -> ability.getAbilityType() == AbilityType.MANA + && ((ManaAbility) ability).isPoolDependant()); + } + @Override public Abilities getProtectionAbilities() { return stream() @@ -224,7 +232,7 @@ public class AbilitiesImpl extends ArrayList implements Ab @Override public boolean contains(T ability) { - for (Iterator iterator = this.iterator(); iterator.hasNext(); ) { // simple loop can cause java.util.ConcurrentModificationException + for (Iterator iterator = this.iterator(); iterator.hasNext();) { // simple loop can cause java.util.ConcurrentModificationException 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 // already before the copy. Because then it keeps the triggered ability twice and it triggers twice. diff --git a/Mage/src/main/java/mage/abilities/effects/mana/ManaEffect.java b/Mage/src/main/java/mage/abilities/effects/mana/ManaEffect.java index 471574c20da..208da3035af 100644 --- a/Mage/src/main/java/mage/abilities/effects/mana/ManaEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/mana/ManaEffect.java @@ -80,6 +80,22 @@ public abstract class ManaEffect extends OneShotEffect { 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 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 * 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)); } - /** * 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) diff --git a/Mage/src/main/java/mage/abilities/mana/ActivatedManaAbilityImpl.java b/Mage/src/main/java/mage/abilities/mana/ActivatedManaAbilityImpl.java index d24268a6180..02fdec3317c 100644 --- a/Mage/src/main/java/mage/abilities/mana/ActivatedManaAbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/mana/ActivatedManaAbilityImpl.java @@ -19,6 +19,7 @@ public abstract class ActivatedManaAbilityImpl extends ActivatedAbilityImpl impl protected List netMana = new ArrayList<>(); protected boolean undoPossible; + protected boolean poolDependant; public ActivatedManaAbilityImpl(Zone zone, ManaEffect effect, Cost cost) { super(AbilityType.MANA, zone); @@ -36,6 +37,7 @@ public abstract class ActivatedManaAbilityImpl extends ActivatedAbilityImpl impl super(ability); this.netMana.addAll(ability.netMana); this.undoPossible = ability.undoPossible; + this.poolDependant = ability.poolDependant; } @Override @@ -101,6 +103,23 @@ public abstract class ActivatedManaAbilityImpl extends ActivatedAbilityImpl impl return netManaCopy; } + @Override + public List getNetMana(Game game, Mana possibleManaInPool) { + if (isPoolDependant()) { + List poolDependantNetMana = new ArrayList<>(); + for (Effect effect : getEffects()) { + if (effect instanceof ManaEffect) { + List effectNetMana = ((ManaEffect) effect).getNetMana(game, possibleManaInPool, this); + if (effectNetMana != null) { + poolDependantNetMana.addAll(effectNetMana); + } + } + } + return poolDependantNetMana; + } + return getNetMana(game); + } + @Override public Set getProducableManaTypes(Game game) { Set 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 * library) *

- * TODO: it helps with single mana activate for mana pool, but will not work while activates on paying for casting - * (e.g. user can cheats to see next draw card) + * TODO: it helps with single mana activate for mana pool, but will not work + * while activates on paying for casting (e.g. user can cheats to see next + * draw card) * * @return */ @@ -140,4 +160,15 @@ public abstract class ActivatedManaAbilityImpl extends ActivatedAbilityImpl impl this.undoPossible = undoPossible; } + @Override + public boolean isPoolDependant() { + return poolDependant; + } + + @Override + public ActivatedManaAbilityImpl setPoolDependant(boolean poolDependant) { + this.poolDependant = poolDependant; + return this; + } + } diff --git a/Mage/src/main/java/mage/abilities/mana/ManaAbility.java b/Mage/src/main/java/mage/abilities/mana/ManaAbility.java index 2db974fa804..2a3bcfd1fe8 100644 --- a/Mage/src/main/java/mage/abilities/mana/ManaAbility.java +++ b/Mage/src/main/java/mage/abilities/mana/ManaAbility.java @@ -8,6 +8,7 @@ package mage.abilities.mana; import java.util.List; import java.util.Set; import mage.Mana; +import mage.abilities.Ability; import mage.constants.ManaType; import mage.game.Game; @@ -25,6 +26,17 @@ 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()). + * 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 getNetMana(Game game, Mana possibleManaInPool); /** * The type of mana a permanent "could produce" is the type of mana that any @@ -45,4 +57,16 @@ public interface ManaAbility { * @return */ 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); + } diff --git a/Mage/src/main/java/mage/abilities/mana/ManaOptions.java b/Mage/src/main/java/mage/abilities/mana/ManaOptions.java index fb0100b6bb9..07f6dec4ab7 100644 --- a/Mage/src/main/java/mage/abilities/mana/ManaOptions.java +++ b/Mage/src/main/java/mage/abilities/mana/ManaOptions.java @@ -176,6 +176,7 @@ public class ManaOptions extends ArrayList { newMana.add(mana); newMana.add(triggeredManaVariation); this.add(newMana); + wasUsable = true; } } } @@ -190,7 +191,7 @@ public class ManaOptions extends ArrayList { Mana startingMana = prevMana.copy(); Mana manaCosts = ability.getManaCosts().getMana(); 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 add(prevMana); } @@ -219,6 +220,7 @@ public class ManaOptions extends ArrayList { newMana.add(mana); newMana.add(triggeredManaVariation); this.add(newMana); + wasUsable = true; } } } @@ -229,21 +231,8 @@ public class ManaOptions extends ArrayList { for (Mana previousMana : copy) { CombineWithExisting: for (Mana manaOption : ability.getManaCosts().getManaOptions()) { - Mana newMana = new Mana(previousMana); if (previousMana.includesMana(manaOption)) { // costs can be paid - newMana.subtractCost(manaOption); - 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); + wasUsable |= subtractCostAddMana(manaOption, triggeredManaVariation, ability.getCosts().isEmpty(), previousMana, ability, game); } } } @@ -264,6 +253,35 @@ public class ManaOptions extends ArrayList { return wasUsable; } + public boolean addManaPoolDependant(List abilities, Game game) { + boolean wasUsable = false; + if (!abilities.isEmpty()) { + if (abilities.size() == 1) { + ActivatedManaAbilityImpl ability = (ActivatedManaAbilityImpl) abilities.get(0); + List 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 getTriggeredManaVariations(Game game, Ability ability, Mana baseMana) { List baseManaPlusTriggeredMana = new ArrayList<>(); baseManaPlusTriggeredMana.add(baseMana); @@ -391,62 +409,60 @@ public class ManaOptions extends ArrayList { * @param oldManaWasReplaced returns the info if the new complete mana does * 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 repeatable = false; - if ((manaToAdd.countColored() > 0 || manaToAdd.getAny() > 0) && manaToAdd.count() > 0 && onlyManaCosts) { - // deactivated because it does cause loops TODO: Find reason + if (manaToAdd != null && (manaToAdd.countColored() > 0 || manaToAdd.getAny() > 0) && manaToAdd.count() > 0 && onlyManaCosts) { 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(); 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 + 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 possible mana of the old one, so no need to add it after return + if (!currentMana.equalManaValue(currentManaCopy)) { + this.removeEqualMana(currentManaCopy); + } } - } - if (!newCombinations || !repeatable) { - break; + currentManaCopy = newMana.copy(); } } - - } - } 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 + if (!newCombinations || !repeatable) { + break; } } } - forceManaDeduplication(); return oldManaWasReplaced; @@ -457,12 +473,23 @@ public class ManaOptions extends ArrayList { * @param manaAvailable * @return */ - public static List getPossiblePayCombinations(int number, Mana manaAvailable) { + public static List getPossiblePayCombinations(Mana manaCost, Mana manaAvailable) { List payCombinations = new ArrayList<>(); List 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) { - for (int i = 0; i < number; i++) { + for (int i = 0; i < manaCost.getGeneric(); i++) { List existingManas = new ArrayList<>(); if (i > 0) { existingManas.addAll(payCombinations); @@ -472,7 +499,7 @@ public class ManaOptions extends ArrayList { existingManas.add(new Mana()); } for (Mana existingMana : existingManas) { - Mana manaToPayFrom = manaAvailable.copy(); + Mana manaToPayFrom = manaAfterFixedPayment.copy(); manaToPayFrom.subtract(existingMana); if (manaToPayFrom.getBlack() > 0 && !payCombinationsStrings.contains(existingMana.toString() + Mana.BlackMana(1).toString())) { manaToPayFrom.subtract(Mana.BlackMana(1)); @@ -494,6 +521,10 @@ public class ManaOptions extends ArrayList { manaToPayFrom.subtract(Mana.WhiteMana(1)); 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 if (payCombinations.isEmpty() && manaToPayFrom.getAny() > 0 && !payCombinationsStrings.contains(existingMana.toString() + Mana.AnyMana(1).toString())) { manaToPayFrom.subtract(Mana.AnyMana(1)); @@ -502,7 +533,10 @@ public class ManaOptions extends ArrayList { } } } else { - payCombinations.add(Mana.ColorlessMana(number)); + payCombinations.add(Mana.ColorlessMana(manaCost.getGeneric())); + } + for (Mana mana : payCombinations) { + mana.add(fixedMana); } return payCombinations; } @@ -517,6 +551,18 @@ public class ManaOptions extends ArrayList { return false; } + public boolean removeEqualMana(Mana manaToRemove) { + boolean result = false; + for (Iterator 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 payCombinations, List payCombinationsStrings) { Mana newMana = existingMana.copy(); newMana.add(mana); @@ -572,6 +618,7 @@ public class ManaOptions extends ArrayList { return false; } + @Override public String toString() { Iterator it = this.iterator(); if (!it.hasNext()) { diff --git a/Mage/src/main/java/mage/abilities/mana/SimpleManaAbility.java b/Mage/src/main/java/mage/abilities/mana/SimpleManaAbility.java index 76e34410ced..8ed8dcc2c32 100644 --- a/Mage/src/main/java/mage/abilities/mana/SimpleManaAbility.java +++ b/Mage/src/main/java/mage/abilities/mana/SimpleManaAbility.java @@ -71,5 +71,6 @@ public class SimpleManaAbility extends ActivatedManaAbilityImpl { } return new ArrayList<>(netMana); } + } diff --git a/Mage/src/main/java/mage/abilities/mana/TriggeredManaAbility.java b/Mage/src/main/java/mage/abilities/mana/TriggeredManaAbility.java index 85796fec713..f963b2244d4 100644 --- a/Mage/src/main/java/mage/abilities/mana/TriggeredManaAbility.java +++ b/Mage/src/main/java/mage/abilities/mana/TriggeredManaAbility.java @@ -22,6 +22,7 @@ import mage.constants.ManaType; public abstract class TriggeredManaAbility extends TriggeredAbilityImpl implements ManaAbility { protected List netMana = new ArrayList<>(); + protected boolean poolDependant; public TriggeredManaAbility(Zone zone, ManaEffect effect) { this(zone, effect, false); @@ -37,6 +38,7 @@ public abstract class TriggeredManaAbility extends TriggeredAbilityImpl implemen public TriggeredManaAbility(final TriggeredManaAbility ability) { super(ability); this.netMana.addAll(ability.netMana); + this.poolDependant = ability.poolDependant; } /** @@ -60,6 +62,23 @@ public abstract class TriggeredManaAbility extends TriggeredAbilityImpl implemen return new ArrayList<>(netMana); } + @Override + public List getNetMana(Game game, Mana possibleManaInPool) { + if (isPoolDependant()) { + List poolDependantNetMana = new ArrayList<>(); + for (Effect effect : getEffects()) { + if (effect instanceof ManaEffect) { + List effectNetMana = ((ManaEffect) effect).getNetMana(game, possibleManaInPool, this); + if (effectNetMana != null) { + poolDependantNetMana.addAll(effectNetMana); + } + } + } + return poolDependantNetMana; + } + return getNetMana(game); + } + @Override public Set getProducableManaTypes(Game game) { Set manaTypes = new HashSet<>(); @@ -80,4 +99,16 @@ public abstract class TriggeredManaAbility extends TriggeredAbilityImpl implemen public boolean definesMana(Game game) { return !netMana.isEmpty(); } + + @Override + public boolean isPoolDependant() { + return poolDependant; + } + + @Override + public TriggeredManaAbility setPoolDependant(boolean poolDependant) { + this.poolDependant = poolDependant; + return this; + } + } diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index a4de6ee5b3e..0180576e9e9 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -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 Boolean canUse = null; boolean canAdd = false; - boolean withCost = false; + boolean useLater = false; // sources with mana costs or mana pool dependency Abilities manaAbilities = permanent.getAbilities().getAvailableActivatedManaAbilities(Zone.BATTLEFIELD, playerId, game); // returns ability only if canActivate is true for (Iterator it = manaAbilities.iterator(); it.hasNext();) { @@ -2907,7 +2907,7 @@ public abstract class PlayerImpl implements Player, Serializable { if (!ability.hasTapCost()) { it.remove(); Abilities noTapAbilities = new AbilitiesImpl<>(ability); - if (ability.getManaCosts().isEmpty()) { + if (ability.getManaCosts().isEmpty() && !ability.isPoolDependant()) { sourceWithoutManaCosts.add(noTapAbilities); } else { sourceWithCosts.add(noTapAbilities); @@ -2916,14 +2916,14 @@ public abstract class PlayerImpl implements Player, Serializable { } canAdd = true; - if (!ability.getManaCosts().isEmpty()) { - withCost = true; + if (!ability.getManaCosts().isEmpty() || ability.isPoolDependant()) { + useLater = true; break; } } } if (canAdd) { - if (withCost) { + if (useLater) { sourceWithCosts.add(manaAbilities); } else { sourceWithoutManaCosts.add(manaAbilities); @@ -2936,17 +2936,29 @@ public abstract class PlayerImpl implements Player, Serializable { } boolean anAbilityWasUsed = true; + boolean usePoolDependantAbilities = false; // use such abilities later than other if possible because it can maximize mana production while (anAbilityWasUsed && !sourceWithCosts.isEmpty()) { anAbilityWasUsed = false; for (Iterator> iterator = sourceWithCosts.iterator(); iterator.hasNext();) { Abilities manaAbilities = iterator.next(); - boolean used = availableMana.addManaWithCost(manaAbilities, game); - if (used) { - iterator.remove(); - availableMana.removeDuplicated(); - anAbilityWasUsed = true; + if (usePoolDependantAbilities || !manaAbilities.hasPoolDependantAbilities()) { + boolean used; + if (manaAbilities.hasPoolDependantAbilities()) { + used = availableMana.addManaPoolDependant(manaAbilities, game); + } 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)