From 7ad7d5f03d5dabc264cbf179e9705d3c3ba65175 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Thu, 16 Jul 2020 12:44:01 +0200 Subject: [PATCH] * Fixed available mana generation for Caged Sun and storage lands (e.g. Calciform Pools) related to #6698. --- Mage.Sets/src/mage/cards/c/CagedSun.java | 18 +- .../src/mage/cards/c/CalciformPools.java | 13 +- Mage.Sets/src/mage/cards/d/DreadshipReef.java | 9 +- Mage.Sets/src/mage/cards/f/FungalReaches.java | 9 +- .../src/mage/cards/m/MoltenSlagheap.java | 9 +- .../src/mage/cards/s/SaltcrustedSteppe.java | 9 +- .../mage/cards/s/SelvalaHeartOfTheWilds.java | 5 +- .../mage/test/cards/mana/ManaSourceTest.java | 2 - .../cards/mana/TappedForManaRelatedTest.java | 109 ++++++++++ Mage/src/main/java/mage/Mana.java | 75 +++++-- .../mana/AddManaInAnyCombinationEffect.java | 67 ++++-- .../java/mage/abilities/mana/ManaOptions.java | 200 ++++++++++-------- .../main/java/mage/players/PlayerImpl.java | 15 +- 13 files changed, 383 insertions(+), 157 deletions(-) diff --git a/Mage.Sets/src/mage/cards/c/CagedSun.java b/Mage.Sets/src/mage/cards/c/CagedSun.java index 4567bca5d17..92d37d47ce0 100644 --- a/Mage.Sets/src/mage/cards/c/CagedSun.java +++ b/Mage.Sets/src/mage/cards/c/CagedSun.java @@ -1,5 +1,8 @@ package mage.cards.c; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; import mage.Mana; import mage.ObjectColor; import mage.abilities.Ability; @@ -18,8 +21,6 @@ import mage.game.events.GameEvent; import mage.game.events.GameEvent.EventType; import mage.game.permanent.Permanent; -import java.util.UUID; - /** * @author BetaSteward */ @@ -135,6 +136,19 @@ class CagedSunEffect extends ManaEffect { super(effect); } + @Override + public List getNetMana(Game game, Ability source) { + if (game != null && game.inCheckPlayableState()) { + ObjectColor color = (ObjectColor) game.getState().getValue(source.getSourceId() + "_color"); + if (color != null) { + List availableNetMana = new ArrayList<>(); + availableNetMana.add(new Mana(ColoredManaSymbol.lookup(color.toString().charAt(0)))); + return availableNetMana; + } + } + return super.getNetMana(game, source); + } + @Override public Mana produceMana(Game game, Ability source) { if (game != null) { diff --git a/Mage.Sets/src/mage/cards/c/CalciformPools.java b/Mage.Sets/src/mage/cards/c/CalciformPools.java index 9d123691915..e5e46e936ac 100644 --- a/Mage.Sets/src/mage/cards/c/CalciformPools.java +++ b/Mage.Sets/src/mage/cards/c/CalciformPools.java @@ -1,4 +1,3 @@ - package mage.cards.c; import java.util.UUID; @@ -7,9 +6,10 @@ import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.RemoveVariableCountersSourceCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.dynamicvalue.common.CountersSourceCount; import mage.abilities.dynamicvalue.common.RemovedCountersForCostValue; -import mage.abilities.effects.mana.AddManaInAnyCombinationEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.effects.mana.AddManaInAnyCombinationEffect; import mage.abilities.mana.ColorlessManaAbility; import mage.abilities.mana.SimpleManaAbility; import mage.cards.CardImpl; @@ -26,17 +26,18 @@ import mage.counters.CounterType; public final class CalciformPools extends CardImpl { public CalciformPools(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.LAND},""); + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); - // {tap}: Add {C}. + // {T}: Add {C}. this.addAbility(new ColorlessManaAbility()); - // {1}, {tap}: Put a storage counter on Calciform Pools. + // {1}, {T}: Put a storage counter on Calciform Pools. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.STORAGE.createInstance()), new GenericManaCost(1)); ability.addCost(new TapSourceCost()); this.addAbility(ability); // {1}, Remove X storage counters from Calciform Pools: Add X mana in any combination of {W} and/or {U}. ability = new SimpleManaAbility(Zone.BATTLEFIELD, - new AddManaInAnyCombinationEffect(RemovedCountersForCostValue.instance, ColoredManaSymbol.W, ColoredManaSymbol.U), + new AddManaInAnyCombinationEffect(RemovedCountersForCostValue.instance, + new CountersSourceCount(CounterType.STORAGE), ColoredManaSymbol.W, ColoredManaSymbol.U), new GenericManaCost(1)); ability.addCost(new RemoveVariableCountersSourceCost(CounterType.STORAGE.createInstance())); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/d/DreadshipReef.java b/Mage.Sets/src/mage/cards/d/DreadshipReef.java index 53a75729b99..504c0f04f59 100644 --- a/Mage.Sets/src/mage/cards/d/DreadshipReef.java +++ b/Mage.Sets/src/mage/cards/d/DreadshipReef.java @@ -1,4 +1,3 @@ - package mage.cards.d; import java.util.UUID; @@ -7,9 +6,10 @@ import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.RemoveVariableCountersSourceCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.dynamicvalue.common.CountersSourceCount; import mage.abilities.dynamicvalue.common.RemovedCountersForCostValue; -import mage.abilities.effects.mana.AddManaInAnyCombinationEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.effects.mana.AddManaInAnyCombinationEffect; import mage.abilities.mana.ColorlessManaAbility; import mage.abilities.mana.SimpleManaAbility; import mage.cards.CardImpl; @@ -26,7 +26,7 @@ import mage.counters.CounterType; public final class DreadshipReef extends CardImpl { public DreadshipReef(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.LAND},""); + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); // {tap}: Add {C}. this.addAbility(new ColorlessManaAbility()); @@ -36,7 +36,8 @@ public final class DreadshipReef extends CardImpl { this.addAbility(ability); // {1}, Remove X storage counters from Dreadship Reef: Add X mana in any combination of {U} and/or {B}. ability = new SimpleManaAbility(Zone.BATTLEFIELD, - new AddManaInAnyCombinationEffect(RemovedCountersForCostValue.instance, ColoredManaSymbol.U, ColoredManaSymbol.B), + new AddManaInAnyCombinationEffect(RemovedCountersForCostValue.instance, + new CountersSourceCount(CounterType.STORAGE), ColoredManaSymbol.U, ColoredManaSymbol.B), new GenericManaCost(1)); ability.addCost(new RemoveVariableCountersSourceCost(CounterType.STORAGE.createInstance())); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/f/FungalReaches.java b/Mage.Sets/src/mage/cards/f/FungalReaches.java index 69f0a3cbe66..c94f030bbc8 100644 --- a/Mage.Sets/src/mage/cards/f/FungalReaches.java +++ b/Mage.Sets/src/mage/cards/f/FungalReaches.java @@ -1,4 +1,3 @@ - package mage.cards.f; import java.util.UUID; @@ -7,9 +6,10 @@ import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.RemoveVariableCountersSourceCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.dynamicvalue.common.CountersSourceCount; import mage.abilities.dynamicvalue.common.RemovedCountersForCostValue; -import mage.abilities.effects.mana.AddManaInAnyCombinationEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.effects.mana.AddManaInAnyCombinationEffect; import mage.abilities.mana.ColorlessManaAbility; import mage.abilities.mana.SimpleManaAbility; import mage.cards.CardImpl; @@ -26,7 +26,7 @@ import mage.counters.CounterType; public final class FungalReaches extends CardImpl { public FungalReaches(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.LAND},""); + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); // {tap}: Add {C}. this.addAbility(new ColorlessManaAbility()); @@ -38,7 +38,8 @@ public final class FungalReaches extends CardImpl { // {1}, Remove X storage counters from Fungal Reaches: Add X mana in any combination of {R} and/or {G}. ability = new SimpleManaAbility(Zone.BATTLEFIELD, - new AddManaInAnyCombinationEffect(RemovedCountersForCostValue.instance, ColoredManaSymbol.R, ColoredManaSymbol.G), + new AddManaInAnyCombinationEffect(RemovedCountersForCostValue.instance, + new CountersSourceCount(CounterType.STORAGE), ColoredManaSymbol.R, ColoredManaSymbol.G), new GenericManaCost(1)); ability.addCost(new RemoveVariableCountersSourceCost(CounterType.STORAGE.createInstance())); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/m/MoltenSlagheap.java b/Mage.Sets/src/mage/cards/m/MoltenSlagheap.java index 5d339c274aa..7bdc03fbdde 100644 --- a/Mage.Sets/src/mage/cards/m/MoltenSlagheap.java +++ b/Mage.Sets/src/mage/cards/m/MoltenSlagheap.java @@ -1,4 +1,3 @@ - package mage.cards.m; import java.util.UUID; @@ -7,9 +6,10 @@ import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.RemoveVariableCountersSourceCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.dynamicvalue.common.CountersSourceCount; import mage.abilities.dynamicvalue.common.RemovedCountersForCostValue; -import mage.abilities.effects.mana.AddManaInAnyCombinationEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.effects.mana.AddManaInAnyCombinationEffect; import mage.abilities.mana.ColorlessManaAbility; import mage.abilities.mana.SimpleManaAbility; import mage.cards.CardImpl; @@ -26,7 +26,7 @@ import mage.counters.CounterType; public final class MoltenSlagheap extends CardImpl { public MoltenSlagheap(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.LAND},""); + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); // {tap}: Add {C}. this.addAbility(new ColorlessManaAbility()); @@ -36,7 +36,8 @@ public final class MoltenSlagheap extends CardImpl { this.addAbility(ability); // {1}, Remove X storage counters from Molten Slagheap: Add X mana in any combination of {B} and/or {R}. ability = new SimpleManaAbility(Zone.BATTLEFIELD, - new AddManaInAnyCombinationEffect(RemovedCountersForCostValue.instance, ColoredManaSymbol.B, ColoredManaSymbol.R), + new AddManaInAnyCombinationEffect(RemovedCountersForCostValue.instance, + new CountersSourceCount(CounterType.STORAGE), ColoredManaSymbol.B, ColoredManaSymbol.R), new GenericManaCost(1)); ability.addCost(new RemoveVariableCountersSourceCost(CounterType.STORAGE.createInstance())); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/s/SaltcrustedSteppe.java b/Mage.Sets/src/mage/cards/s/SaltcrustedSteppe.java index 9b20de817dc..e21e5415ca2 100644 --- a/Mage.Sets/src/mage/cards/s/SaltcrustedSteppe.java +++ b/Mage.Sets/src/mage/cards/s/SaltcrustedSteppe.java @@ -1,4 +1,3 @@ - package mage.cards.s; import java.util.UUID; @@ -7,9 +6,10 @@ import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.RemoveVariableCountersSourceCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.dynamicvalue.common.CountersSourceCount; import mage.abilities.dynamicvalue.common.RemovedCountersForCostValue; -import mage.abilities.effects.mana.AddManaInAnyCombinationEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.effects.mana.AddManaInAnyCombinationEffect; import mage.abilities.mana.ColorlessManaAbility; import mage.abilities.mana.SimpleManaAbility; import mage.cards.CardImpl; @@ -26,7 +26,7 @@ import mage.counters.CounterType; public final class SaltcrustedSteppe extends CardImpl { public SaltcrustedSteppe(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.LAND},""); + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); // {tap}: Add {C}. this.addAbility(new ColorlessManaAbility()); @@ -36,7 +36,8 @@ public final class SaltcrustedSteppe extends CardImpl { this.addAbility(ability); // {1}, Remove X storage counters from Saltcrusted Steppe: Add X mana in any combination of {G} and/or {W}. ability = new SimpleManaAbility(Zone.BATTLEFIELD, - new AddManaInAnyCombinationEffect(RemovedCountersForCostValue.instance, ColoredManaSymbol.G, ColoredManaSymbol.W), + new AddManaInAnyCombinationEffect(RemovedCountersForCostValue.instance, + new CountersSourceCount(CounterType.STORAGE), ColoredManaSymbol.G, ColoredManaSymbol.W), new GenericManaCost(1)); ability.addCost(new RemoveVariableCountersSourceCost(CounterType.STORAGE.createInstance())); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/s/SelvalaHeartOfTheWilds.java b/Mage.Sets/src/mage/cards/s/SelvalaHeartOfTheWilds.java index fdbd148e2df..533634fe294 100644 --- a/Mage.Sets/src/mage/cards/s/SelvalaHeartOfTheWilds.java +++ b/Mage.Sets/src/mage/cards/s/SelvalaHeartOfTheWilds.java @@ -1,4 +1,3 @@ - package mage.cards.s; import java.util.UUID; @@ -51,7 +50,7 @@ public final class SelvalaHeartOfTheWilds extends CardImpl { // {G}, {T}: Add X mana in any combination of colors, where X is the greatest power among creatures you control. ManaEffect manaEffect = new AddManaInAnyCombinationEffect( - GreatestPowerAmongControlledCreaturesValue.instance, rule2, + GreatestPowerAmongControlledCreaturesValue.instance, GreatestPowerAmongControlledCreaturesValue.instance, rule2, ColoredManaSymbol.B, ColoredManaSymbol.U, ColoredManaSymbol.R, ColoredManaSymbol.W, ColoredManaSymbol.G); Ability ability = new SimpleManaAbility(Zone.BATTLEFIELD, manaEffect, new ManaCostsImpl("{G}")); ability.addCost(new TapSourceCost()); @@ -130,4 +129,4 @@ class GreatestPowerPredicate implements Predicate { public String toString() { return "Greatest Power"; } -} \ No newline at end of file +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/mana/ManaSourceTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/mana/ManaSourceTest.java index 358442d8b9a..f381bc6b392 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/mana/ManaSourceTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/mana/ManaSourceTest.java @@ -1,4 +1,3 @@ - package org.mage.test.cards.mana; import mage.constants.PhaseStep; @@ -35,7 +34,6 @@ public class ManaSourceTest extends CardTestPlayerBase { assertPermanentCount(playerB, "Myr Superion", 0); assertHandCount(playerB, "Myr Superion", 1); - } } 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 a38dc947255..b8684b32a37 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 @@ -3,6 +3,7 @@ package org.mage.test.cards.mana; import mage.abilities.mana.ManaOptions; import mage.constants.PhaseStep; import mage.constants.Zone; +import mage.counters.CounterType; import org.junit.Assert; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -90,4 +91,112 @@ public class TappedForManaRelatedTest extends CardTestPlayerBase { } + @Test + public void TestCalciformPools() { + // {T}: Add {C}. + // {1}, {T}: Put a storage counter on Calciform Pools. + // {1}, Remove X storage counters from Calciform Pools: Add X mana in any combination of {W} and/or {U}. + addCard(Zone.BATTLEFIELD, playerA, "Calciform Pools", 1); + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + Assert.assertEquals("mana variations don't fit", 1, manaOptions.size()); + assertManaOptions("{C}", manaOptions); + + } + + @Test + public void TestCalciformPools2Counter() { + // {T}: Add {C}. + // {1}, {T}: Put a storage counter on Calciform Pools. + // {1}, Remove X storage counters from Calciform Pools: Add X mana in any combination of {W} and/or {U}. + addCard(Zone.BATTLEFIELD, playerA, "Calciform Pools", 1); + addCounters(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Calciform Pools", CounterType.STORAGE, 2); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertCounterCount("Calciform Pools", CounterType.STORAGE, 2); + + ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + Assert.assertEquals("mana variations don't fit", 4, manaOptions.size()); + assertManaOptions("{W}{W}", manaOptions); + assertManaOptions("{W}{U}", manaOptions); + assertManaOptions("{U}{U}", manaOptions); + assertManaOptions("{C}", manaOptions); + } + + @Test + public void TestCalciformPools2CounterAndTrigger() { + setStrictChooseMode(true); + // {T}: Add {C}. + // {1}, {T}: Put a storage counter on Calciform Pools. + // {1}, Remove X storage counters from Calciform Pools: Add X mana in any combination of {W} and/or {U}. + addCard(Zone.BATTLEFIELD, playerA, "Calciform Pools", 1); + addCounters(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Calciform Pools", CounterType.STORAGE, 2); + + // As Caged Sun enters the battlefield, choose a color. + // Creatures you control of the chosen color get +1/+1. + // Whenever a land's ability adds one or more mana of the chosen color, add one additional mana of that color. + addCard(Zone.BATTLEFIELD, playerA, "Caged Sun", 1); + setChoice(playerA, "White"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertAllCommandsUsed(); + + assertCounterCount("Calciform Pools", CounterType.STORAGE, 2); + + ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + Assert.assertEquals("mana variations don't fit", 4, manaOptions.size()); + assertManaOptions("{W}{W}{W}", manaOptions); + assertManaOptions("{W}{W}{U}", manaOptions); + assertManaOptions("{U}{U}", manaOptions); + assertManaOptions("{C}", manaOptions); + } + + @Test + public void TestCastleSengir() { + setStrictChooseMode(true); + // {T}: Add Colorless. + // {1}, {T}: Add Black. + // {2}, {T}: Add Blue or Red. + addCard(Zone.BATTLEFIELD, playerA, "Castle Sengir", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertAllCommandsUsed(); + + ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + Assert.assertEquals("mana variations don't fit", 2, manaOptions.size()); + assertManaOptions("{C}{R}", manaOptions); + assertManaOptions("{B}", manaOptions); + } + + @Test + public void TestCastleSengir2() { + setStrictChooseMode(true); + // {T}: Add {C}. + // {1}, {T}: Add {B}. + // {2}, {T}: Add {U} or {R}. + addCard(Zone.BATTLEFIELD, playerA, "Castle Sengir", 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertAllCommandsUsed(); + + ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + Assert.assertEquals("mana variations don't fit", 4, manaOptions.size()); + assertManaOptions("{C}{W}{W}", manaOptions); + assertManaOptions("{W}{B}", manaOptions); + assertManaOptions("{U}", manaOptions); + assertManaOptions("{R}", manaOptions); + } } diff --git a/Mage/src/main/java/mage/Mana.java b/Mage/src/main/java/mage/Mana.java index f6a594292fb..ae8f0f3604f 100644 --- a/Mage/src/main/java/mage/Mana.java +++ b/Mage/src/main/java/mage/Mana.java @@ -43,13 +43,13 @@ public class Mana implements Comparable, Serializable, Copyable { * Creates a {@link Mana} object with the passed in values. Values can not * be less than 0. Any values less than 0 will be logged and set to 0. * - * @param red total Red mana to have. - * @param green total Green mana to have. - * @param blue total Blue mana to have. - * @param white total White mana to have. - * @param black total Black mana to have. - * @param generic total Generic mana to have. - * @param any total Any mana to have. + * @param red total Red mana to have. + * @param green total Green mana to have. + * @param blue total Blue mana to have. + * @param white total White mana to have. + * @param black total Black mana to have. + * @param generic total Generic mana to have. + * @param any total Any mana to have. * @param colorless total Colorless mana to have. */ public Mana(final int red, final int green, final int blue, final int white, final int black, final int generic, final int any, final int colorless) { @@ -142,6 +142,35 @@ public class Mana implements Comparable, Serializable, Copyable { } } + public Mana(final ManaType manaType, int num) { + Objects.requireNonNull(manaType, "The passed in ManaType can not be null"); + switch (manaType) { + case GREEN: + green = num; + break; + case RED: + red = num; + break; + case BLACK: + black = num; + break; + case BLUE: + blue = num; + break; + case WHITE: + white = num; + break; + case COLORLESS: + colorless = num; + break; + case GENERIC: + generic = num; + break; + default: + throw new IllegalArgumentException("Unknown manaType: " + manaType); + } + } + /** * Creates a {@link Mana} object with the passed in {@code num} of Red mana. * {@code num} can not be a negative value. Negative values will be logged @@ -161,7 +190,7 @@ public class Mana implements Comparable, Serializable, Copyable { * * @param num value of Green mana to create. * @return a {@link Mana} object with the passed in {@code num} of Green - * mana. + * mana. */ public static Mana GreenMana(int num) { return new Mana(0, notNegative(num, "Green"), 0, 0, 0, 0, 0, 0); @@ -174,7 +203,7 @@ public class Mana implements Comparable, Serializable, Copyable { * * @param num value of Blue mana to create. * @return a {@link Mana} object with the passed in {@code num} of Blue - * mana. + * mana. */ public static Mana BlueMana(int num) { return new Mana(0, 0, notNegative(num, "Blue"), 0, 0, 0, 0, 0); @@ -187,7 +216,7 @@ public class Mana implements Comparable, Serializable, Copyable { * * @param num value of White mana to create. * @return a {@link Mana} object with the passed in {@code num} of White - * mana. + * mana. */ public static Mana WhiteMana(int num) { return new Mana(0, 0, 0, notNegative(num, "White"), 0, 0, 0, 0); @@ -200,7 +229,7 @@ public class Mana implements Comparable, Serializable, Copyable { * * @param num value of Black mana to create. * @return a {@link Mana} object with the passed in {@code num} of Black - * mana. + * mana. */ public static Mana BlackMana(int num) { return new Mana(0, 0, 0, 0, notNegative(num, "Black"), 0, 0, 0); @@ -213,7 +242,7 @@ public class Mana implements Comparable, Serializable, Copyable { * * @param num value of Generic mana to create. * @return a {@link Mana} object with the passed in {@code num} of Generic - * mana. + * mana. */ public static Mana GenericMana(int num) { return new Mana(0, 0, 0, 0, 0, notNegative(num, "Generic"), 0, 0); @@ -226,7 +255,7 @@ public class Mana implements Comparable, Serializable, Copyable { * * @param num value of Colorless mana to create. * @return a {@link Mana} object with the passed in {@code num} of Colorless - * mana. + * mana. */ public static Mana ColorlessMana(int num) { return new Mana(0, 0, 0, 0, 0, 0, 0, notNegative(num, "Colorless")); @@ -444,7 +473,7 @@ public class Mana implements Comparable, Serializable, Copyable { * * @param filter the colors of mana to return the count for. * @return the count of filtered mana provided by the passed in - * {@link FilterMana}. + * {@link FilterMana}. */ public int count(final FilterMana filter) { if (filter == null) { @@ -898,10 +927,10 @@ public class Mana implements Comparable, Serializable, Copyable { * Returns if this objects mana contains any coloured mana the same as the * passed in {@link Mana}'s mana. * - * @param mana the mana to check for + * @param mana the mana to check for * @param includeColorless also check for colorless * @return true if this contains any of the same type of coloured mana that - * this has + * this has */ public boolean containsAny(final Mana mana, boolean includeColorless) { if (mana.black > 0 && this.black > 0) { @@ -929,7 +958,7 @@ public class Mana implements Comparable, Serializable, Copyable { * * @param color the color to return the count for. * @return the total count of mana in this object as specified by the passed - * in {@link ColoredManaSymbol}. + * in {@link ColoredManaSymbol}. */ public int getColor(final ColoredManaSymbol color) { if (color == ColoredManaSymbol.G) { @@ -956,7 +985,7 @@ public class Mana implements Comparable, Serializable, Copyable { * * @param manaType the type to return the count for. * @return the total count of mana in this object as specified by the passed - * in {@link ManaType}. + * in {@link ManaType}. */ public int get(final ManaType manaType) { switch (manaType) { @@ -981,7 +1010,7 @@ public class Mana implements Comparable, Serializable, Copyable { * {@code amount} . * * @param manaType the color of the mana to set - * @param amount the value to set the mana too + * @param amount the value to set the mana too */ public void set(final ManaType manaType, final int amount) { switch (manaType) { @@ -1056,7 +1085,7 @@ public class Mana implements Comparable, Serializable, Copyable { * * @param mana the mana to compare with * @return if this object has more than or equal mana to the passed in - * {@link Mana}. + * {@link Mana}. */ public boolean includesMana(Mana mana) { return this.green >= mana.green @@ -1217,10 +1246,10 @@ public class Mana implements Comparable, Serializable, Copyable { * is negative, it is logged and 0 is returned. * * @param value the value to check. - * @param name the name of the value to check. Used to make logging of the - * {@code value} easier + * @param name the name of the value to check. Used to make logging of the + * {@code value} easier * @return the {@code value} passed in, unless it is minus, in which case 0 - * is returned. + * is returned. */ private static int notNegative(int value, final String name) { if (value < 0) { diff --git a/Mage/src/main/java/mage/abilities/effects/mana/AddManaInAnyCombinationEffect.java b/Mage/src/main/java/mage/abilities/effects/mana/AddManaInAnyCombinationEffect.java index 82b3e1fe5cc..db671d85346 100644 --- a/Mage/src/main/java/mage/abilities/effects/mana/AddManaInAnyCombinationEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/mana/AddManaInAnyCombinationEffect.java @@ -1,5 +1,8 @@ package mage.abilities.effects.mana; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import mage.Mana; import mage.abilities.Ability; import mage.abilities.dynamicvalue.DynamicValue; @@ -10,10 +13,6 @@ import mage.game.Game; import mage.players.Player; import mage.util.CardUtil; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - /** * @author LevelX2 */ @@ -21,20 +20,22 @@ public class AddManaInAnyCombinationEffect extends ManaEffect { private ArrayList manaSymbols = new ArrayList<>(); private final DynamicValue amount; + private final DynamicValue netAmount; public AddManaInAnyCombinationEffect(int amount) { - this(StaticValue.get(amount), ColoredManaSymbol.B, ColoredManaSymbol.U, ColoredManaSymbol.R, ColoredManaSymbol.W, ColoredManaSymbol.G); + this(StaticValue.get(amount), StaticValue.get(amount), ColoredManaSymbol.B, ColoredManaSymbol.U, ColoredManaSymbol.R, ColoredManaSymbol.W, ColoredManaSymbol.G); } public AddManaInAnyCombinationEffect(int amount, ColoredManaSymbol... coloredManaSymbols) { - this(StaticValue.get(amount), coloredManaSymbols); + this(StaticValue.get(amount), StaticValue.get(amount), coloredManaSymbols); } - public AddManaInAnyCombinationEffect(DynamicValue amount, ColoredManaSymbol... coloredManaSymbols) { + public AddManaInAnyCombinationEffect(DynamicValue amount, DynamicValue netAmount, ColoredManaSymbol... coloredManaSymbols) { super(); this.manaSymbols.addAll(Arrays.asList(coloredManaSymbols)); this.amount = amount; this.staticText = setText(); + this.netAmount = netAmount; } public AddManaInAnyCombinationEffect(int amount, String text) { @@ -47,8 +48,8 @@ public class AddManaInAnyCombinationEffect extends ManaEffect { this.staticText = text; } - public AddManaInAnyCombinationEffect(DynamicValue amount, String text, ColoredManaSymbol... coloredManaSymbols) { - this(amount, coloredManaSymbols); + public AddManaInAnyCombinationEffect(DynamicValue amount, DynamicValue netAmount, String text, ColoredManaSymbol... coloredManaSymbols) { + this(amount, netAmount, coloredManaSymbols); this.staticText = text; } @@ -56,6 +57,11 @@ public class AddManaInAnyCombinationEffect extends ManaEffect { super(effect); this.manaSymbols = effect.manaSymbols; this.amount = effect.amount; + if (effect.netAmount != null) { + this.netAmount = effect.netAmount.copy(); + } else { + this.netAmount = null; + } } @Override @@ -67,16 +73,51 @@ public class AddManaInAnyCombinationEffect extends ManaEffect { public List getNetMana(Game game, Ability source) { List netMana = new ArrayList<>(); if (game != null) { - int amountOfManaLeft = amount.calculate(game, source, this); - if (amountOfManaLeft > 0) { - netMana.add(Mana.AnyMana(amountOfManaLeft)); + if (game.inCheckPlayableState()) { + int amountAvailableMana = netAmount.calculate(game, source, this); + if (amountAvailableMana > 0) { + if (manaSymbols.size() == 5) { // Any color + netMana.add(new Mana(0, 0, 0, 0, 0, 0, amountAvailableMana, 0)); + } else { + generatePossibleManaCombinations(netMana, manaSymbols, amountAvailableMana); + } + } + } else { + int amountOfManaLeft = amount.calculate(game, source, this); + if (amountOfManaLeft > 0) { + netMana.add(Mana.AnyMana(amountOfManaLeft)); + } } } return netMana; } + private void generatePossibleManaCombinations(List combinations, ArrayList manaSymbols, int amountAvailableMana) { + List copy = new ArrayList<>(); + for (int i = 0; i < amountAvailableMana; i++) { + for (ColoredManaSymbol colorSymbol : manaSymbols) { + if (i == 0) { + combinations.add(new Mana(colorSymbol)); + } else { + for (Mana prevMana : copy) { + Mana newMana = new Mana(); + newMana.add(prevMana); + newMana.add(new Mana(colorSymbol)); + combinations.add(newMana); + } + } + } + if (i + 1 < amountAvailableMana) { + copy.clear(); + copy.addAll(combinations); + combinations.clear(); + } + } + } + @Override - public Mana produceMana(Game game, Ability source) { + public Mana produceMana(Game game, Ability source + ) { Player player = game.getPlayer(source.getControllerId()); if (player != null) { Mana mana = new Mana(); diff --git a/Mage/src/main/java/mage/abilities/mana/ManaOptions.java b/Mage/src/main/java/mage/abilities/mana/ManaOptions.java index 60f57856a29..106f7edf2e8 100644 --- a/Mage/src/main/java/mage/abilities/mana/ManaOptions.java +++ b/Mage/src/main/java/mage/abilities/mana/ManaOptions.java @@ -45,7 +45,7 @@ public class ManaOptions extends ArrayList { //if there is only one mana option available add it to all the existing options List netManas = abilities.get(0).getNetMana(game); if (netManas.size() == 1) { - checkTappedForManaReplacement(abilities.get(0), game, netManas.get(0)); + checkManaReplacementAndTriggeredMana(abilities.get(0), game, netManas.get(0)); addMana(netManas.get(0)); addTriggeredMana(game, abilities.get(0)); } else if (netManas.size() > 1) { @@ -58,7 +58,7 @@ public class ManaOptions extends ArrayList { this.clear(); for (ActivatedManaAbilityImpl ability : abilities) { for (Mana netMana : ability.getNetMana(game)) { - checkTappedForManaReplacement(ability, game, netMana); + checkManaReplacementAndTriggeredMana(ability, game, netMana); for (Mana triggeredManaVariation : getTriggeredManaVariations(game, ability, netMana)) { SkipAddMana: for (Mana mana : copy) { @@ -91,7 +91,7 @@ public class ManaOptions extends ArrayList { this.clear(); for (Mana netMana : netManas) { for (Mana mana : copy) { - if (!hasTapCost(ability) || checkTappedForManaReplacement(ability, game, netMana)) { + if (!hasTapCost(ability) || checkManaReplacementAndTriggeredMana(ability, game, netMana)) { Mana newMana = new Mana(); newMana.add(mana); newMana.add(netMana); @@ -111,19 +111,30 @@ public class ManaOptions extends ArrayList { return newList; } - private boolean checkTappedForManaReplacement(Ability ability, Game game, Mana mana) { + /** + * Generates triggered mana and checks replacement of Tapped_For_Mana event. + * Also generates triggered mana for MANA_ADDED event. + * + * @param ability + * @param game + * @param mana + * @return false if mana production was completely replaced + */ + private boolean checkManaReplacementAndTriggeredMana(Ability ability, Game game, Mana mana) { if (hasTapCost(ability)) { ManaEvent event = new ManaEvent(GameEvent.EventType.TAPPED_FOR_MANA, ability.getSourceId(), ability.getSourceId(), ability.getControllerId(), mana); - if (!game.replaceEvent(event)) { - game.fireEvent(event); - return true; + if (game.replaceEvent(event)) { + return false; } - return false; + game.fireEvent(event); } + ManaEvent manaEvent = new ManaEvent(GameEvent.EventType.MANA_ADDED, ability.getSourceId(), ability.getSourceId(), ability.getControllerId(), mana); + manaEvent.setData(mana.toString()); + game.fireEvent(manaEvent); return true; } - private boolean hasTapCost(Ability ability) { + public boolean hasTapCost(Ability ability) { for (Cost cost : ability.getCosts()) { if (cost instanceof TapSourceCost) { return true; @@ -135,50 +146,47 @@ public class ManaOptions extends ArrayList { public void addManaWithCost(List abilities, Game game) { int replaces = 0; if (isEmpty()) { - this.add(new Mana()); + this.add(new Mana()); // needed if this is the first available mana, otherwise looping over existing options woold not loop } if (!abilities.isEmpty()) { if (abilities.size() == 1) { - //if there is only one mana option available add it to all the existing options - ActivatedManaAbilityImpl ability = abilities.get(0); List netManas = abilities.get(0).getNetMana(game); - // no mana costs - if (ability.getManaCosts().isEmpty()) { - if (netManas.size() == 1) { - checkTappedForManaReplacement(ability, game, netManas.get(0)); - addMana(netManas.get(0)); - addTriggeredMana(game, ability); - } else { - List copy = copy(); - this.clear(); - for (Mana netMana : netManas) { - checkTappedForManaReplacement(ability, game, netMana); - for (Mana triggeredManaVariation : getTriggeredManaVariations(game, ability, netMana)) { - for (Mana mana : copy) { - Mana newMana = new Mana(); - newMana.add(mana); - newMana.add(triggeredManaVariation); - this.add(newMana); + if (netManas.size() > 0) { // ability can produce mana + ActivatedManaAbilityImpl ability = abilities.get(0); + // The ability has no mana costs + if (ability.getManaCosts().isEmpty()) { // No mana costs, so no mana to subtract from available + if (netManas.size() == 1) { + checkManaReplacementAndTriggeredMana(ability, game, netManas.get(0)); + addMana(netManas.get(0)); + addTriggeredMana(game, ability); + } else { + List copy = copy(); + this.clear(); + for (Mana netMana : netManas) { + checkManaReplacementAndTriggeredMana(ability, game, netMana); + for (Mana triggeredManaVariation : getTriggeredManaVariations(game, ability, netMana)) { + for (Mana mana : copy) { + Mana newMana = new Mana(); + newMana.add(mana); + newMana.add(triggeredManaVariation); + this.add(newMana); + } } } } - } - } else // the ability has mana costs - if (netManas.size() == 1) { - checkTappedForManaReplacement(ability, game, netManas.get(0)); - subtractCostAddMana(ability.getManaCosts().getMana(), netManas.get(0), ability.getCosts().isEmpty()); - addTriggeredMana(game, ability); - } else { - List copy = copy(); - this.clear(); - for (Mana netMana : netManas) { - checkTappedForManaReplacement(ability, game, netMana); - for (Mana triggeredManaVariation : getTriggeredManaVariations(game, ability, netMana)) { - for (Mana mana : copy) { - Mana newMana = new Mana(); - newMana.add(mana); - newMana.add(triggeredManaVariation); - subtractCostAddMana(ability.getManaCosts().getMana(), netMana, ability.getCosts().isEmpty()); + } else {// The ability has mana costs + List copy = copy(); + this.clear(); + for (Mana netMana : netManas) { + checkManaReplacementAndTriggeredMana(ability, game, netMana); + for (Mana triggeredManaVariation : getTriggeredManaVariations(game, ability, netMana)) { + for (Mana prevMana : copy) { + Mana startingMana = prevMana.copy(); + if (!subtractCostAddMana(ability.getManaCosts().getMana(), triggeredManaVariation, ability.getCosts().isEmpty(), startingMana)) { + // the starting mana includes mana parts that the increased mana does not include, so add starting mana also as an option + add(prevMana); + } + } } } } @@ -191,7 +199,7 @@ public class ManaOptions extends ArrayList { List netManas = ability.getNetMana(game); if (ability.getManaCosts().isEmpty()) { for (Mana netMana : netManas) { - checkTappedForManaReplacement(ability, game, netMana); + checkManaReplacementAndTriggeredMana(ability, game, netMana); for (Mana triggeredManaVariation : getTriggeredManaVariations(game, ability, netMana)) { for (Mana mana : copy) { Mana newMana = new Mana(); @@ -203,7 +211,7 @@ public class ManaOptions extends ArrayList { } } else { for (Mana netMana : netManas) { - checkTappedForManaReplacement(ability, game, netMana); + checkManaReplacementAndTriggeredMana(ability, game, netMana); for (Mana triggeredManaVariation : getTriggeredManaVariations(game, ability, netMana)) { for (Mana previousMana : copy) { CombineWithExisting: @@ -322,58 +330,68 @@ public class ManaOptions extends ArrayList { return new ManaOptions(this); } - public void subtractCostAddMana(Mana cost, Mana addMana, boolean onlyManaCosts) { - if (isEmpty()) { - this.add(new Mana()); - } + /** + * Performs the simulation of a mana ability with costs + * + * @param cost cost to use the ability + * @param manaToAdd one mana variation that can be added by using + * this ability + * @param onlyManaCosts flag to know if the costs are mana costs only + * @param currentMana the mana available before the usage of the + * ability + * @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) { + boolean oldManaWasReplaced = false; // true if the newly created mana includes all mana possibilities of the old boolean repeatable = false; - if (addMana.getAny() == 1 && addMana.count() == 1 && onlyManaCosts) { + if (manaToAdd.getAny() == 1 && manaToAdd.count() == 1 && 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 } - List copy = copy(); - this.clear(); - for (Mana mana : copy) { - Mana oldMan = mana.copy(); - if (mana.includesMana(cost)) { // it 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() > (mana.getGeneric() + mana.getColorless())) { - Mana coloredCost = cost.copy(); - coloredCost.setGeneric(0); - mana.subtract(coloredCost); - boolean oldManaWasReplaced = false; - for (Mana payCombination : getPossiblePayCombinations(cost.getGeneric(), mana)) { - Mana newMana = mana.copy(); - newMana.subtract(payCombination); - newMana.add(addMana); - Mana moreValuable = Mana.getMoreValuableMana(oldMan, newMana); - if (!oldMan.equals(moreValuable)) { - this.add(newMana); - if (moreValuable != null) { - oldManaWasReplaced = true; // the new mana includes all possibilities of the old one - } + Mana prevMana = currentMana.copy(); + if (currentMana.includesMana(cost)) { // it 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 } + } - } - if (!oldManaWasReplaced) { - this.add(oldMan); - } - } else { - while (mana.includesMana(cost)) { - mana.subtractCost(cost); - mana.add(addMana); - if (!repeatable) { - break; - } - } - // Don't use mana that only reduce the available mana - if (oldMan.contains(mana) && oldMan.count() > mana.count()) { - mana.setToMana(oldMan); - } - this.add(mana); } + } 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 + } + } + } } + return oldManaWasReplaced; } private List getPossiblePayCombinations(int number, Mana manaAvailable) { diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 32cf353f32e..80893282ffd 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -2890,11 +2890,24 @@ public abstract class PlayerImpl implements Player, Serializable { boolean withCost = false; Abilities manaAbilities = permanent.getAbilities().getAvailableActivatedManaAbilities(Zone.BATTLEFIELD, game); - for (ActivatedManaAbilityImpl ability : manaAbilities) { + for (Iterator it = manaAbilities.iterator(); it.hasNext();) { + ActivatedManaAbilityImpl ability = it.next(); if (canUse == null) { canUse = permanent.canUseActivatedAbilities(game); } if (canUse && ability.canActivate(playerId, game).canActivate()) { + // abilities without Tap costs have to be handled as separate sources, because they can be used also + if (!availableMana.hasTapCost(ability)) { + it.remove(); + Abilities noTapAbilities = new AbilitiesImpl<>(ability); + if (ability.getManaCosts().isEmpty()) { + sourceWithoutManaCosts.add(noTapAbilities); + } else { + sourceWithCosts.add(noTapAbilities); + } + continue; + } + canAdd = true; if (!ability.getManaCosts().isEmpty()) { withCost = true;