From 67dd45c1c7b52465ddd3586d2b260974b896e349 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sun, 2 Aug 2020 10:51:22 +0200 Subject: [PATCH] * Added getProducableManaTypes method for mana effects to handle Squandered Resources or Reflecting Pool like abilities. * Fixes to Soldevi Adnate, Skirge Familiar, Mana Web. --- .../src/mage/cards/k/KatabaticWinds.java | 9 +- .../src/mage/cards/m/MagewrightsStone.java | 4 +- Mage.Sets/src/mage/cards/m/ManaWeb.java | 38 ++-- Mage.Sets/src/mage/cards/p/Powerleech.java | 17 +- Mage.Sets/src/mage/cards/s/SerraBestiary.java | 11 +- .../src/mage/cards/s/SkirgeFamiliar.java | 10 +- Mage.Sets/src/mage/cards/s/SoldeviAdnate.java | 7 +- .../src/mage/cards/s/SquanderedResources.java | 87 ++++----- Mage.Sets/src/mage/cards/s/StarCompass.java | 171 ++---------------- .../mana/NonTappingManaAbilitiesTest.java | 54 ++++++ .../test/cards/mana/ReflectingPoolTest.java | 21 ++- .../org/mage/test/utils/ManaOptionsTest.java | 4 +- Mage/src/main/java/mage/MageObjectImpl.java | 41 ++--- Mage/src/main/java/mage/Mana.java | 5 +- .../src/main/java/mage/abilities/Ability.java | 14 ++ .../main/java/mage/abilities/AbilityImpl.java | 1 + .../abilities/effects/common/ManaEffect.java | 66 +++++-- .../mana/AddManaInAnyCombinationEffect.java | 29 ++- .../effects/mana/BasicManaEffect.java | 48 ++--- .../mana/ActivatedManaAbilityImpl.java | 15 ++ .../mana/AnyColorLandsProduceManaAbility.java | 97 +++++----- .../java/mage/abilities/mana/ManaAbility.java | 14 ++ .../java/mage/abilities/mana/ManaOptions.java | 43 ++--- .../abilities/mana/TriggeredManaAbility.java | 16 +- .../main/java/mage/players/PlayerImpl.java | 2 +- 25 files changed, 420 insertions(+), 404 deletions(-) diff --git a/Mage.Sets/src/mage/cards/k/KatabaticWinds.java b/Mage.Sets/src/mage/cards/k/KatabaticWinds.java index 847459cd0f..3f166e61b9 100644 --- a/Mage.Sets/src/mage/cards/k/KatabaticWinds.java +++ b/Mage.Sets/src/mage/cards/k/KatabaticWinds.java @@ -3,7 +3,6 @@ package mage.cards.k; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; import mage.abilities.effects.RestrictionEffect; import mage.abilities.keyword.FlyingAbility; @@ -119,14 +118,12 @@ class KatabaticWindsRuleModifyingEffect extends ContinuousRuleModifyingEffectImp public boolean applies(GameEvent event, Ability source, Game game) { MageObject object = game.getObject(event.getSourceId()); Optional ability = game.getAbility(event.getTargetId(), event.getSourceId()); - if (ability.isPresent() + return ability.isPresent() && object != null && object.isCreature() && object.getAbilities().contains(FlyingAbility.getInstance()) - && game.getState().getPlayersInRange(source.getControllerId(), game).contains(event.getPlayerId())) { - return ability.get().getCosts().stream().anyMatch((cost) -> (cost instanceof TapSourceCost)); - } - return false; + && game.getState().getPlayersInRange(source.getControllerId(), game).contains(event.getPlayerId()) + && ability.get().hasTapCost(); } @Override diff --git a/Mage.Sets/src/mage/cards/m/MagewrightsStone.java b/Mage.Sets/src/mage/cards/m/MagewrightsStone.java index 83bc556a45..dfe2fc0235 100644 --- a/Mage.Sets/src/mage/cards/m/MagewrightsStone.java +++ b/Mage.Sets/src/mage/cards/m/MagewrightsStone.java @@ -66,10 +66,8 @@ class HasAbilityWithTapSymbolPredicate implements Predicate { for (Ability ability : abilities) { if ((ability.getAbilityType() == AbilityType.ACTIVATED || ability.getAbilityType() == AbilityType.MANA) && !ability.getCosts().isEmpty()) { - for (Cost cost : ability.getCosts()) { - if (cost instanceof TapSourceCost) { + if (ability.hasTapCost()) { return true; - } } } } diff --git a/Mage.Sets/src/mage/cards/m/ManaWeb.java b/Mage.Sets/src/mage/cards/m/ManaWeb.java index 5fce3c5d7b..afc7ce6856 100644 --- a/Mage.Sets/src/mage/cards/m/ManaWeb.java +++ b/Mage.Sets/src/mage/cards/m/ManaWeb.java @@ -2,15 +2,18 @@ package mage.cards.m; import java.util.Objects; +import java.util.Set; import java.util.UUID; import mage.Mana; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.OneShotEffect; import mage.abilities.mana.ActivatedManaAbilityImpl; +import mage.abilities.mana.AnyColorLandsProduceManaAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.ManaType; import mage.constants.Outcome; import mage.constants.Zone; import mage.filter.common.FilterLandPermanent; @@ -61,6 +64,9 @@ class ManaWebTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { + if (game.inCheckPlayableState()) { + return false; + } if (game.getOpponents(controllerId).contains(event.getPlayerId())) { Permanent permanent = game.getPermanent(event.getSourceId()); @@ -103,35 +109,19 @@ class ManaWebeffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = null; - - if (game != null && source != null) { - permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); - } - - if (permanent != null && game != null) { - Mana mana = new Mana(); - - for (ActivatedManaAbilityImpl ability : permanent.getAbilities().getActivatedManaAbilities(Zone.BATTLEFIELD)) { - for (Mana netMana : ability.getNetMana(game)) { - mana.add(netMana); - } - } - + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent != null) { + Set manaTypesSource = AnyColorLandsProduceManaAbility.getManaTypesFromPermanent(permanent, game); boolean tappedLands = false; for (Permanent opponentPermanent : game.getBattlefield().getActivePermanents(filter, permanent.getControllerId(), game)) { if (Objects.equals(opponentPermanent.getControllerId(), permanent.getControllerId())) { - Mana opponentLandMana = new Mana(); - - for (ActivatedManaAbilityImpl ability : opponentPermanent.getAbilities().getAvailableActivatedManaAbilities(Zone.BATTLEFIELD, game)) { - for (Mana netMana : ability.getNetMana(game)) { - opponentLandMana.add(netMana); + Set manaTypes = AnyColorLandsProduceManaAbility.getManaTypesFromPermanent(opponentPermanent, game); + for (ManaType manaType : manaTypes) { + if (manaTypesSource.contains(manaType)) { + tappedLands = opponentPermanent.tap(game) || tappedLands; + break; } } - - if (mana.containsAny(opponentLandMana, true)) { - tappedLands = opponentPermanent.tap(game) || tappedLands; - } } } return tappedLands; diff --git a/Mage.Sets/src/mage/cards/p/Powerleech.java b/Mage.Sets/src/mage/cards/p/Powerleech.java index a1bdb8c836..eda07f710a 100644 --- a/Mage.Sets/src/mage/cards/p/Powerleech.java +++ b/Mage.Sets/src/mage/cards/p/Powerleech.java @@ -3,8 +3,6 @@ package mage.cards.p; import java.util.UUID; import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.costs.Cost; -import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.common.GainLifeEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -25,7 +23,7 @@ public final class Powerleech extends CardImpl { public Powerleech(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{G}{G}"); - // Whenever an artifact an opponent controls becomes tapped or an opponent activates an artifact's ability without {tap} in its activation cost, you gain 1 life. + // Whenever an artifact an opponent controls becomes tapped or an opponent activates an artifact's ability without {T} in its activation cost, you gain 1 life. this.addAbility(new PowerleechTriggeredAbility()); } @@ -75,17 +73,8 @@ class PowerleechTriggeredAbility extends TriggeredAbilityImpl { if (stackAbility == null) { return false; } - boolean triggerable = true; - for (Cost cost : stackAbility.getCosts()) { - if (cost instanceof TapSourceCost) { - triggerable = false; - break; - } - } - if (!triggerable) { - return false; - } - return player.hasOpponent(permanent.getControllerId(), game); + return !stackAbility.hasTapCost() + && player.hasOpponent(permanent.getControllerId(), game); } if (event.getType() == GameEvent.EventType.TAPPED) { Permanent permanent = game.getPermanentOrLKIBattlefield(event.getTargetId()); diff --git a/Mage.Sets/src/mage/cards/s/SerraBestiary.java b/Mage.Sets/src/mage/cards/s/SerraBestiary.java index 4e3892ace4..a571390814 100644 --- a/Mage.Sets/src/mage/cards/s/SerraBestiary.java +++ b/Mage.Sets/src/mage/cards/s/SerraBestiary.java @@ -7,7 +7,6 @@ import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; import mage.abilities.effects.common.AttachEffect; @@ -104,16 +103,12 @@ class SerraBestiaryRuleModifyingEffect extends ContinuousRuleModifyingEffectImpl } MageObject object = game.getObject(event.getSourceId()); Optional ability = game.getAbility(event.getTargetId(), event.getSourceId()); - if (ability.isPresent() + return ability.isPresent() && object != null && object.isCreature() && object.getId().equals(enchantedCreature.getId()) - && game.getState().getPlayersInRange(source.getControllerId(), game).contains(event.getPlayerId())) { - if (ability.get().getCosts().stream().anyMatch((cost) -> (cost instanceof TapSourceCost))) { - return true; - } - } - return false; + && game.getState().getPlayersInRange(source.getControllerId(), game).contains(event.getPlayerId()) + && ability.get().hasTapCost(); } @Override diff --git a/Mage.Sets/src/mage/cards/s/SkirgeFamiliar.java b/Mage.Sets/src/mage/cards/s/SkirgeFamiliar.java index 63d3bce82b..8c7974c18f 100644 --- a/Mage.Sets/src/mage/cards/s/SkirgeFamiliar.java +++ b/Mage.Sets/src/mage/cards/s/SkirgeFamiliar.java @@ -5,6 +5,8 @@ import java.util.UUID; import mage.MageInt; import mage.Mana; import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.dynamicvalue.IntPlusDynamicValue; +import mage.abilities.dynamicvalue.common.CardsInControllerHandCount; import mage.abilities.keyword.FlyingAbility; import mage.abilities.mana.SimpleManaAbility; import mage.cards.CardImpl; @@ -29,7 +31,13 @@ public final class SkirgeFamiliar extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // Discard a card: Add {B}. - this.addAbility(new SimpleManaAbility(Zone.BATTLEFIELD, Mana.BlackMana(1), new DiscardCardCost(false))); + // public SimpleManaAbility(Zone zone, Mana mana, Cost cost, DynamicValue netAmount) { + this.addAbility(new SimpleManaAbility(Zone.BATTLEFIELD, Mana.BlackMana(1), + new DiscardCardCost(false), + // not perfect but for hand cards correct, activated abilities on Battlefield will miss one possible available mana + // to solve this we have to do possible mana calculation per pell/ability to use. + new IntPlusDynamicValue(-1, CardsInControllerHandCount.instance) + )); } public SkirgeFamiliar(final SkirgeFamiliar card) { diff --git a/Mage.Sets/src/mage/cards/s/SoldeviAdnate.java b/Mage.Sets/src/mage/cards/s/SoldeviAdnate.java index 3800b3fac0..26aa39dd92 100644 --- a/Mage.Sets/src/mage/cards/s/SoldeviAdnate.java +++ b/Mage.Sets/src/mage/cards/s/SoldeviAdnate.java @@ -7,6 +7,8 @@ import mage.Mana; import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.dynamicvalue.common.HighestCMCOfPermanentValue; import mage.abilities.dynamicvalue.common.SacrificeCostConvertedMana; import mage.abilities.mana.DynamicManaAbility; import mage.cards.CardImpl; @@ -38,8 +40,9 @@ public final class SoldeviAdnate extends CardImpl { this.toughness = new MageInt(2); // {T}, Sacrifice a black or artifact creature: Add an amount of {B} equal to the sacrificed creature's converted mana cost. - Ability ability = new DynamicManaAbility(Mana.BlackMana(1), new SacrificeCostConvertedMana("creature"), - "add an amount of {B} equal to the sacrificed creature's converted mana cost"); + Ability ability = new DynamicManaAbility(Mana.BlackMana(1), new SacrificeCostConvertedMana("creature"), new TapSourceCost(), + "add an amount of {B} equal to the sacrificed creature's converted mana cost" , false, + new HighestCMCOfPermanentValue(filter, true)); ability.addCost(new SacrificeTargetCost(new TargetControlledPermanent(filter))); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/s/SquanderedResources.java b/Mage.Sets/src/mage/cards/s/SquanderedResources.java index 803fca497e..6076bbdd18 100644 --- a/Mage.Sets/src/mage/cards/s/SquanderedResources.java +++ b/Mage.Sets/src/mage/cards/s/SquanderedResources.java @@ -13,7 +13,6 @@ import mage.cards.CardSetInfo; import mage.choices.Choice; import mage.choices.ChoiceColor; import mage.constants.CardType; -import mage.constants.ColoredManaSymbol; import mage.constants.Zone; import mage.filter.common.FilterControlledLandPermanent; import mage.filter.common.FilterControlledPermanent; @@ -23,8 +22,13 @@ import mage.players.Player; import mage.target.common.TargetControlledPermanent; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.UUID; +import mage.abilities.mana.AnyColorLandsProduceManaAbility; +import mage.constants.ManaType; +import mage.filter.StaticFilters; /** * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) @@ -64,24 +68,35 @@ class SquanderedResourcesEffect extends ManaEffect { @Override public List getNetMana(Game game, Ability source) { List netManas = new ArrayList<>(); - Mana types = getManaTypes(game, source); - if (types.getBlack() > 0) { - netManas.add(new Mana(ColoredManaSymbol.B)); + Set manaTypes = new HashSet<>(); + if (game != null && game.inCheckPlayableState()) { + for (Permanent land : game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_LAND, source.getControllerId(), game)) { + manaTypes.addAll(AnyColorLandsProduceManaAbility.getManaTypesFromPermanent(land, game)); + } + } else { + manaTypes = getManaTypesFromSacrificedPermanent(game, source); } - if (types.getRed() > 0) { - netManas.add(new Mana(ColoredManaSymbol.R)); - } - if (types.getBlue() > 0) { - netManas.add(new Mana(ColoredManaSymbol.U)); - } - if (types.getGreen() > 0) { - netManas.add(new Mana(ColoredManaSymbol.G)); - } - if (types.getWhite() > 0) { - netManas.add(new Mana(ColoredManaSymbol.W)); - } - if (types.getGeneric() > 0) { - netManas.add(Mana.ColorlessMana(1)); + if (manaTypes.size() == 5 && !manaTypes.contains(ManaType.COLORLESS) && !manaTypes.contains(ManaType.GENERIC)) { + netManas.add(Mana.AnyMana(1)); + } else { + if (manaTypes.contains(ManaType.BLACK)) { + netManas.add(Mana.BlackMana(1)); + } + if (manaTypes.contains(ManaType.RED)) { + netManas.add(Mana.RedMana(1)); + } + if (manaTypes.contains(ManaType.BLUE)) { + netManas.add(Mana.BlueMana(1)); + } + if (manaTypes.contains(ManaType.GREEN)) { + netManas.add(Mana.GreenMana(1)); + } + if (manaTypes.contains(ManaType.WHITE)) { + netManas.add(Mana.WhiteMana(1)); + } + if (manaTypes.contains(ManaType.COLORLESS)) { + netManas.add(Mana.ColorlessMana(1)); + } } return netManas; } @@ -92,34 +107,26 @@ class SquanderedResourcesEffect extends ManaEffect { if (game == null) { return mana; } - Mana types = getManaTypes(game, source); + Set manaTypes = getManaTypesFromSacrificedPermanent(game, source); Choice choice = new ChoiceColor(true); choice.getChoices().clear(); choice.setMessage("Pick a mana color"); - if (types.getBlack() > 0) { + if (manaTypes.contains(ManaType.BLACK)) { choice.getChoices().add("Black"); } - if (types.getRed() > 0) { + if (manaTypes.contains(ManaType.RED)) { choice.getChoices().add("Red"); } - if (types.getBlue() > 0) { + if (manaTypes.contains(ManaType.BLUE)) { choice.getChoices().add("Blue"); } - if (types.getGreen() > 0) { + if (manaTypes.contains(ManaType.GREEN)) { choice.getChoices().add("Green"); } - if (types.getWhite() > 0) { + if (manaTypes.contains(ManaType.WHITE)) { choice.getChoices().add("White"); } - if (types.getColorless() > 0) { - choice.getChoices().add("Colorless"); - } - if (types.getAny() > 0) { - choice.getChoices().add("Black"); - choice.getChoices().add("Red"); - choice.getChoices().add("Blue"); - choice.getChoices().add("Green"); - choice.getChoices().add("White"); + if (manaTypes.contains(ManaType.COLORLESS)) { choice.getChoices().add("Colorless"); } if (!choice.getChoices().isEmpty()) { @@ -158,20 +165,14 @@ class SquanderedResourcesEffect extends ManaEffect { return mana; } - private Mana getManaTypes(Game game, Ability source) { - Mana types = new Mana(); + private Set getManaTypesFromSacrificedPermanent(Game game, Ability source) { + Set types = new HashSet<>(); for (Cost cost : source.getCosts()) { if (cost instanceof SacrificeTargetCost && !((SacrificeTargetCost) cost).getPermanents().isEmpty()) { Permanent land = ((SacrificeTargetCost) cost).getPermanents().get(0); if (land != null) { - Abilities manaAbilities = land.getAbilities().getActivatedManaAbilities(Zone.BATTLEFIELD); - for (ActivatedManaAbilityImpl ability : manaAbilities) { - if (!ability.equals(source) && ability.definesMana(game)) { - for (Mana netMana : ability.getNetMana(game)) { - types.add(netMana); - } - } - } + types.addAll(AnyColorLandsProduceManaAbility.getManaTypesFromPermanent(land, game)); + break; } } } diff --git a/Mage.Sets/src/mage/cards/s/StarCompass.java b/Mage.Sets/src/mage/cards/s/StarCompass.java index bc8d0b475a..8e22b80c78 100644 --- a/Mage.Sets/src/mage/cards/s/StarCompass.java +++ b/Mage.Sets/src/mage/cards/s/StarCompass.java @@ -1,43 +1,34 @@ package mage.cards.s; -import mage.Mana; -import mage.abilities.Abilities; -import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTappedAbility; -import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.effects.common.ManaEffect; -import mage.abilities.mana.ActivatedManaAbilityImpl; -import mage.abilities.mana.SimpleManaAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.choices.Choice; -import mage.choices.ChoiceColor; import mage.constants.CardType; -import mage.constants.ColoredManaSymbol; import mage.constants.SuperType; -import mage.constants.Zone; import mage.filter.common.FilterControlledLandPermanent; import mage.filter.common.FilterControlledPermanent; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.players.Player; - -import java.util.ArrayList; -import java.util.List; import java.util.UUID; +import mage.abilities.mana.AnyColorLandsProduceManaAbility; +import mage.constants.TargetController; /** * @author anonymous */ public final class StarCompass extends CardImpl { + + private static final FilterControlledPermanent filter = new FilterControlledLandPermanent(); + static { + filter.add(SuperType.BASIC.getPredicate()); + } + public StarCompass(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); // Star Compass enters the battlefield tapped. this.addAbility(new EntersBattlefieldTappedAbility()); - // {tap}: Add one mana of any color that a basic land you control could produce. - this.addAbility(new SimpleManaAbility(Zone.BATTLEFIELD, new StarCompassManaEffect(), new TapSourceCost())); + // {T}: Add one mana of any color that a basic land you control could produce. + this.addAbility(new AnyColorLandsProduceManaAbility(TargetController.YOU, true, filter)); } public StarCompass(final StarCompass card) { @@ -48,144 +39,4 @@ public final class StarCompass extends CardImpl { public StarCompass copy() { return new StarCompass(this); } -} - -class StarCompassManaEffect extends ManaEffect { - - private static final FilterControlledPermanent filter = new FilterControlledLandPermanent(); - - static { - filter.add(SuperType.BASIC.getPredicate()); - } - - public StarCompassManaEffect() { - super(); - staticText = "Add one mana of any type that a basic land you control could produce"; - } - - public StarCompassManaEffect(final StarCompassManaEffect effect) { - super(effect); - } - - @Override - public List getNetMana(Game game, Ability source) { - List netManas = new ArrayList<>(); - Mana types = getManaTypes(game, source); - if (types.getBlack() > 0) { - netManas.add(new Mana(ColoredManaSymbol.B)); - } - if (types.getRed() > 0) { - netManas.add(new Mana(ColoredManaSymbol.R)); - } - if (types.getBlue() > 0) { - netManas.add(new Mana(ColoredManaSymbol.U)); - } - if (types.getGreen() > 0) { - netManas.add(new Mana(ColoredManaSymbol.G)); - } - if (types.getWhite() > 0) { - netManas.add(new Mana(ColoredManaSymbol.W)); - } - if (types.getGeneric() > 0) { - netManas.add(Mana.ColorlessMana(1)); - } - return netManas; - } - - @Override - public Mana produceMana(Game game, Ability source) { - Mana mana = new Mana(); - if (game == null) { - return mana; - } - Mana types = getManaTypes(game, source); - Choice choice = new ChoiceColor(true); - choice.getChoices().clear(); - choice.setMessage("Pick a mana color"); - if (types.getBlack() > 0) { - choice.getChoices().add("Black"); - } - if (types.getRed() > 0) { - choice.getChoices().add("Red"); - } - if (types.getBlue() > 0) { - choice.getChoices().add("Blue"); - } - if (types.getGreen() > 0) { - choice.getChoices().add("Green"); - } - if (types.getWhite() > 0) { - choice.getChoices().add("White"); - } - if (types.getColorless() > 0) { - choice.getChoices().add("Colorless"); - } - if (types.getAny() > 0) { - choice.getChoices().add("Black"); - choice.getChoices().add("Red"); - choice.getChoices().add("Blue"); - choice.getChoices().add("Green"); - choice.getChoices().add("White"); - choice.getChoices().add("Colorless"); - } - if (!choice.getChoices().isEmpty()) { - Player player = game.getPlayer(source.getControllerId()); - if (player == null) { - return null; - } - if (choice.getChoices().size() == 1) { - choice.setChoice(choice.getChoices().iterator().next()); - } else { - if (!player.choose(outcome, choice, game)) { - return mana; - } - } - if (choice.getChoice() != null) { - switch (choice.getChoice()) { - case "Black": - mana.setBlack(1); - break; - case "Blue": - mana.setBlue(1); - break; - case "Red": - mana.setRed(1); - break; - case "Green": - mana.setGreen(1); - break; - case "White": - mana.setWhite(1); - break; - case "Colorless": - mana.setColorless(1); - break; - } - } - } - return mana; - } - - private Mana getManaTypes(Game game, Ability source) { - Mana types = new Mana(); - if (game != null) { - List lands = game.getBattlefield().getActivePermanents(filter, source.getControllerId(), game); - for (Permanent land : lands) { - Abilities manaAbilities = land.getAbilities().getActivatedManaAbilities(Zone.BATTLEFIELD); - for (ActivatedManaAbilityImpl ability : manaAbilities) { - if (!ability.equals(source) && ability.definesMana(game)) { - for (Mana netMana : ability.getNetMana(game)) { - types.add(netMana); - } - } - } - } - } - return types; - } - - @Override - public StarCompassManaEffect copy() { - return new StarCompassManaEffect(this); - } -} +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/mana/NonTappingManaAbilitiesTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/mana/NonTappingManaAbilitiesTest.java index 81cc312023..f23ff170a0 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/mana/NonTappingManaAbilitiesTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/mana/NonTappingManaAbilitiesTest.java @@ -201,4 +201,58 @@ public class NonTappingManaAbilitiesTest extends CardTestPlayerBase { Assert.assertEquals("mana variations don't fit", 1, manaOptions.size()); assertManaOptions("{R}{R}{R}", manaOptions); } + + @Test + public void TestSquanderedResources() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + addCard(Zone.BATTLEFIELD, playerA, "Taiga", 1); // ({T}: Add {R} or {G}.) + // {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); + // {T}: Add {U}. If you played a land this turn, add {B} instead. + addCard(Zone.BATTLEFIELD, playerA, "River of Tears", 1); + + + // Sacrifice a land: Add one mana of any type the sacrificed land could produce. + addCard(Zone.BATTLEFIELD, playerA, "Squandered Resources", 1); + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertAllCommandsUsed(); + + ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + Assert.assertEquals("mana variations don't fit", 9, manaOptions.size()); + assertManaOptions("{C}{U}{R}{R}{G}", manaOptions); + assertManaOptions("{C}{U}{U}{G}{G}", manaOptions); + assertManaOptions("{C}{U}{U}{R}{G}", manaOptions); + assertManaOptions("{C}{U}{G}{G}{G}", manaOptions); + assertManaOptions("{C}{U}{R}{G}{G}", manaOptions); + assertManaOptions("{C}{W}{U}{G}{G}", manaOptions); + assertManaOptions("{C}{W}{U}{R}{G}", manaOptions); + assertManaOptions("{C}{C}{U}{G}{G}", manaOptions); + assertManaOptions("{C}{C}{U}{R}{G}", manaOptions); + } + + @Test + public void TestSquanderedResourcesWithManaConfluence() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + // {T}, Pay 1 life: Add one mana of any color. + addCard(Zone.BATTLEFIELD, playerA, "Mana Confluence", 1); + + // Sacrifice a land: Add one mana of any type the sacrificed land could produce. + addCard(Zone.BATTLEFIELD, playerA, "Squandered Resources", 1); + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertAllCommandsUsed(); + + ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); + Assert.assertEquals("mana variations don't fit", 1, manaOptions.size()); + assertManaOptions("{G}{Any}{Any}", manaOptions); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/mana/ReflectingPoolTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/mana/ReflectingPoolTest.java index 6778a29a28..7dad6a73ad 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/mana/ReflectingPoolTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/mana/ReflectingPoolTest.java @@ -288,8 +288,27 @@ public class ReflectingPoolTest extends CardTestPlayerBase { execute(); ManaOptions options = playerA.getAvailableManaTest(currentGame); - Assert.assertEquals("Player A should be able to create only 3 different mana options", 2, options.size()); + Assert.assertEquals("Player A should be able to create only 2 different mana options", 2, options.size()); assertManaOptions("{C}{C}{Any}", options); assertManaOptions("{C}{Any}{Any}", options); } + + @Test + public void testWithCalciformPools() { + // {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); + // {T}: Add one mana of any type that a land you control could produce. + addCard(Zone.BATTLEFIELD, playerA, "Reflecting Pool", 1); + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + ManaOptions options = playerA.getAvailableManaTest(currentGame); + Assert.assertEquals("Player A should be able to create only 3 different mana options", 3, options.size()); + assertManaOptions("{C}{C}", options); + assertManaOptions("{C}{W}", options); + assertManaOptions("{C}{U}", options); + } } 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 16dd0fd727..632e2b4d26 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 @@ -321,9 +321,11 @@ public class ManaOptionsTest extends CardTestPlayerBase { execute(); ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame); - Assert.assertEquals("mana variations don't fit", 1, manaOptions.size()); + Assert.assertEquals("mana variations don't fit", 3, manaOptions.size()); assertDuplicatedManaOptions(manaOptions); + assertManaOptions("{C}{C}", manaOptions); assertManaOptions("{Any}{Any}", manaOptions); + assertManaOptions("{C}{Any}", manaOptions); } @Test diff --git a/Mage/src/main/java/mage/MageObjectImpl.java b/Mage/src/main/java/mage/MageObjectImpl.java index 17e9bd22d3..2183f20377 100644 --- a/Mage/src/main/java/mage/MageObjectImpl.java +++ b/Mage/src/main/java/mage/MageObjectImpl.java @@ -170,39 +170,22 @@ public abstract class MageObjectImpl implements MageObject { // its frame colors. if (this.isLand()) { ObjectColor cl = frameColor.copy(); + Set manaTypes = EnumSet.noneOf(ManaType.class); for (Ability ab : getAbilities()) { if (ab instanceof ActivatedManaAbilityImpl) { - ActivatedManaAbilityImpl mana = (ActivatedManaAbilityImpl) ab; - try { - List manaAdded = mana.getNetMana(game); - for (Mana m : manaAdded) { - if (m.getAny() > 0) { - return new ObjectColor("WUBRG"); - } - if (m.getWhite() > 0) { - cl.setWhite(true); - } - if (m.getBlue() > 0) { - cl.setBlue(true); - } - if (m.getBlack() > 0) { - cl.setBlack(true); - } - if (m.getRed() > 0) { - cl.setRed(true); - } - if (m.getGreen() > 0) { - cl.setGreen(true); - } - } - } catch (NullPointerException e) { - // Ability depends on game - // but no game passed - // All such abilities are 5-color ones - return new ObjectColor("WUBRG"); - } + manaTypes.addAll(((ActivatedManaAbilityImpl) ab).getProducableManaTypes(game)); } } + cl.setWhite(manaTypes.contains(ManaType.WHITE)); + cl.setBlue(manaTypes.contains(ManaType.BLUE)); + cl.setBlack(manaTypes.contains(ManaType.BLACK)); + cl.setRed(manaTypes.contains(ManaType.RED)); + cl.setGreen(manaTypes.contains(ManaType.GREEN)); + +// // Ability depends on game +// // but no game passed +// // All such abilities are 5-color ones +// return new ObjectColor("WUBRG"); return cl; } else { // For everything else, just return the frame colors diff --git a/Mage/src/main/java/mage/Mana.java b/Mage/src/main/java/mage/Mana.java index ae8f0f3604..883ec29e2a 100644 --- a/Mage/src/main/java/mage/Mana.java +++ b/Mage/src/main/java/mage/Mana.java @@ -1152,10 +1152,7 @@ public class Mana implements Comparable, Serializable, Copyable { } } if (lessMana.getColorless() > moreMana.getColorless()) { - anyDiff -= lessMana.getColorless() - moreMana.getColorless(); - if (anyDiff < 0) { - return null; - } + return null; // Any (color) can't produce colorless mana } if (lessMana.getAny() > moreMana.getAny()) { return null; diff --git a/Mage/src/main/java/mage/abilities/Ability.java b/Mage/src/main/java/mage/abilities/Ability.java index 24f1083f07..66dd169e6a 100644 --- a/Mage/src/main/java/mage/abilities/Ability.java +++ b/Mage/src/main/java/mage/abilities/Ability.java @@ -23,6 +23,7 @@ import mage.watchers.Watcher; import java.io.Serializable; import java.util.List; import java.util.UUID; +import mage.abilities.costs.common.TapSourceCost; /** * Practically everything in the game is started from an Ability. This interface @@ -366,6 +367,19 @@ public interface Ability extends Controllable, Serializable { * @return */ boolean hasSourceObjectAbility(Game game, MageObject source, GameEvent event); + + /** + * Returns true if the ability has a tap itself in their costs + * @return + */ + default boolean hasTapCost() { + for (Cost cost : this.getCosts()) { + if (cost instanceof TapSourceCost) { + return true; + } + } + return false; + } /** * Returns true if this ability has to be shown as topmost of all the rules diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index b71b8d18f2..c9f951defa 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -35,6 +35,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.UUID; +import mage.abilities.costs.common.TapSourceCost; /** * @author BetaSteward_at_googlemail.com diff --git a/Mage/src/main/java/mage/abilities/effects/common/ManaEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ManaEffect.java index 12dddc1452..cc3ea6300b 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ManaEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ManaEffect.java @@ -13,24 +13,23 @@ import mage.game.events.ManaEvent; import mage.players.Player; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import mage.abilities.TriggeredAbility; +import mage.constants.ManaType; /** * @author BetaSteward_at_googlemail.com */ public abstract class ManaEffect extends OneShotEffect { - protected Mana createdMana; - public ManaEffect() { super(Outcome.PutManaInPool); - createdMana = null; } public ManaEffect(final ManaEffect effect) { super(effect); - this.createdMana = effect.createdMana == null ? null : effect.createdMana.copy(); } @Override @@ -81,6 +80,53 @@ public abstract class ManaEffect extends OneShotEffect { return netMana; } + /** + * 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 + * applicable replacement effects. If the type of mana can’t be defined, + * there’s no type of mana that that permanent could produce. The "type" of + * mana is its color, or lack thereof (for colorless mana). + * + * @param game + * @param source + * @return + */ + public Set getProducableManaTypes(Game game, Ability source) { + return getManaTypesFromManaList(getNetMana(game, source)); + } + + public static Set getManaTypesFromManaList(List manaList) { + Set manaTypes = new HashSet<>(); + for (Mana mana : manaList) { + if (mana.getAny() > 0) { + manaTypes.add(ManaType.BLACK); + manaTypes.add(ManaType.BLUE); + manaTypes.add(ManaType.GREEN); + manaTypes.add(ManaType.WHITE); + manaTypes.add(ManaType.RED); + } + if (mana.getBlack() > 0) { + manaTypes.add(ManaType.BLACK); + } + if (mana.getBlue() > 0) { + manaTypes.add(ManaType.BLUE); + } + if (mana.getGreen() > 0) { + manaTypes.add(ManaType.GREEN); + } + if (mana.getWhite() > 0) { + manaTypes.add(ManaType.WHITE); + } + if (mana.getRed() > 0) { + manaTypes.add(ManaType.RED); + } + if (mana.getColorless() > 0) { + manaTypes.add(ManaType.COLORLESS); + } + } + return manaTypes; + } + /** * 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) @@ -105,14 +151,10 @@ public abstract class ManaEffect extends OneShotEffect { * @param source */ public void checkToFirePossibleEvents(Mana mana, Game game, Ability source) { - if (source.getAbilityType() == AbilityType.MANA) { - for (Cost cost : source.getCosts()) { - if (cost instanceof TapSourceCost) { - ManaEvent event = new ManaEvent(GameEvent.EventType.TAPPED_FOR_MANA, source.getSourceId(), source.getSourceId(), source.getControllerId(), mana); - if (!game.replaceEvent(event)) { - game.fireEvent(event); - } - } + if (source.getAbilityType() == AbilityType.MANA && source.hasTapCost()) { + ManaEvent event = new ManaEvent(GameEvent.EventType.TAPPED_FOR_MANA, source.getSourceId(), source.getSourceId(), source.getControllerId(), mana); + if (!game.replaceEvent(event)) { + game.fireEvent(event); } } } 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 fb780a76d2..bc80fc7fd7 100644 --- a/Mage/src/main/java/mage/abilities/effects/mana/AddManaInAnyCombinationEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/mana/AddManaInAnyCombinationEffect.java @@ -2,7 +2,9 @@ package mage.abilities.effects.mana; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import mage.Mana; import mage.abilities.Ability; @@ -11,6 +13,7 @@ import mage.abilities.dynamicvalue.common.StaticValue; import mage.abilities.effects.common.ManaEffect; import mage.abilities.mana.ManaOptions; import mage.constants.ColoredManaSymbol; +import mage.constants.ManaType; import mage.game.Game; import mage.players.Player; import mage.util.CardUtil; @@ -101,8 +104,7 @@ public class AddManaInAnyCombinationEffect extends ManaEffect { } @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(); @@ -130,6 +132,29 @@ public class AddManaInAnyCombinationEffect extends ManaEffect { return null; } + @Override + public Set getProducableManaTypes(Game game, Ability source) { + Set manaTypes = new HashSet<>(); + for(ColoredManaSymbol coloredManaSymbol: manaSymbols) { + if (coloredManaSymbol.equals(ColoredManaSymbol.B)) { + manaTypes.add(ManaType.BLACK); + } + if (coloredManaSymbol.equals(ColoredManaSymbol.R)) { + manaTypes.add(ManaType.RED); + } + if (coloredManaSymbol.equals(ColoredManaSymbol.G)) { + manaTypes.add(ManaType.GREEN); + } + if (coloredManaSymbol.equals(ColoredManaSymbol.U)) { + manaTypes.add(ManaType.BLUE); + } + if (coloredManaSymbol.equals(ColoredManaSymbol.W)) { + manaTypes.add(ManaType.WHITE); + } + } + return manaTypes; + } + private String setText() { StringBuilder sb = new StringBuilder("Add "); sb.append(CardUtil.numberToText(amount.toString())); diff --git a/Mage/src/main/java/mage/abilities/effects/mana/BasicManaEffect.java b/Mage/src/main/java/mage/abilities/effects/mana/BasicManaEffect.java index 9c1f272348..e50e521ce8 100644 --- a/Mage/src/main/java/mage/abilities/effects/mana/BasicManaEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/mana/BasicManaEffect.java @@ -47,29 +47,31 @@ public class BasicManaEffect extends ManaEffect { // calculate the maximum available mana int count = netAmount.calculate(game, source, this); Mana computedMana = new Mana(); - if (manaTemplate.getBlack() > 0) { - computedMana.setBlack(count * manaTemplate.getBlack()); - } - if (manaTemplate.getBlue() > 0) { - computedMana.setBlue(count * manaTemplate.getBlue()); - } - if (manaTemplate.getGreen() > 0) { - computedMana.setGreen(count * manaTemplate.getGreen()); - } - if (manaTemplate.getRed() > 0) { - computedMana.setRed(count * manaTemplate.getRed()); - } - if (manaTemplate.getWhite() > 0) { - computedMana.setWhite(count * manaTemplate.getWhite()); - } - if (manaTemplate.getColorless() > 0) { - computedMana.setColorless(count * manaTemplate.getColorless()); - } - if (manaTemplate.getAny() > 0) { - throw new IllegalArgumentException("BasicManaEffect does not support {Any} mana!"); - } - if (manaTemplate.getGeneric() > 0) { - computedMana.setGeneric(count * manaTemplate.getGeneric()); + if (count > 0) { + if (manaTemplate.getBlack() > 0) { + computedMana.setBlack(count * manaTemplate.getBlack()); + } + if (manaTemplate.getBlue() > 0) { + computedMana.setBlue(count * manaTemplate.getBlue()); + } + if (manaTemplate.getGreen() > 0) { + computedMana.setGreen(count * manaTemplate.getGreen()); + } + if (manaTemplate.getRed() > 0) { + computedMana.setRed(count * manaTemplate.getRed()); + } + if (manaTemplate.getWhite() > 0) { + computedMana.setWhite(count * manaTemplate.getWhite()); + } + if (manaTemplate.getColorless() > 0) { + computedMana.setColorless(count * manaTemplate.getColorless()); + } + if (manaTemplate.getAny() > 0) { + throw new IllegalArgumentException("BasicManaEffect does not support {Any} mana!"); + } + if (manaTemplate.getGeneric() > 0) { + computedMana.setGeneric(count * manaTemplate.getGeneric()); + } } return new ArrayList<>(Arrays.asList(computedMana)); } diff --git a/Mage/src/main/java/mage/abilities/mana/ActivatedManaAbilityImpl.java b/Mage/src/main/java/mage/abilities/mana/ActivatedManaAbilityImpl.java index ecc635bdaa..a2aa47149f 100644 --- a/Mage/src/main/java/mage/abilities/mana/ActivatedManaAbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/mana/ActivatedManaAbilityImpl.java @@ -1,15 +1,19 @@ package mage.abilities.mana; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.UUID; import mage.Mana; +import mage.abilities.Ability; import mage.abilities.ActivatedAbilityImpl; import mage.abilities.costs.Cost; import mage.abilities.effects.Effect; import mage.abilities.effects.common.ManaEffect; import mage.constants.AbilityType; import mage.constants.AsThoughEffectType; +import mage.constants.ManaType; import mage.constants.TimingRule; import mage.constants.Zone; import mage.game.Game; @@ -105,6 +109,17 @@ public abstract class ActivatedManaAbilityImpl extends ActivatedAbilityImpl impl return netManaCopy; } + @Override + public Set getProducableManaTypes(Game game) { + Set manaTypes = new HashSet<>(); + for (Effect effect : getEffects()) { + if (effect instanceof ManaEffect) { + manaTypes.addAll(((ManaEffect) effect).getProducableManaTypes(game, this)); + } + } + return manaTypes; + } + /** * Used to check if the ability itself defines mana types it can produce. * diff --git a/Mage/src/main/java/mage/abilities/mana/AnyColorLandsProduceManaAbility.java b/Mage/src/main/java/mage/abilities/mana/AnyColorLandsProduceManaAbility.java index 981e5ac83d..b57d040745 100644 --- a/Mage/src/main/java/mage/abilities/mana/AnyColorLandsProduceManaAbility.java +++ b/Mage/src/main/java/mage/abilities/mana/AnyColorLandsProduceManaAbility.java @@ -17,7 +17,10 @@ import mage.game.permanent.Permanent; import mage.players.Player; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import mage.constants.ManaType; /** * @author LevelX2 @@ -55,6 +58,16 @@ public class AnyColorLandsProduceManaAbility extends ActivatedManaAbilityImpl { return true; } + public static Set getManaTypesFromPermanent(Permanent permanent, Game game) { + Set allTypes = new HashSet<>(); + if (permanent != null) { + Abilities manaAbilities = permanent.getAbilities().getActivatedManaAbilities(Zone.BATTLEFIELD); + for (ActivatedManaAbilityImpl ability : manaAbilities) { + allTypes.addAll(ability.getProducableManaTypes(game)); + } + } + return allTypes; + } } class AnyColorLandsProduceManaEffect extends ManaEffect { @@ -87,27 +100,30 @@ class AnyColorLandsProduceManaEffect extends ManaEffect { @Override public List getNetMana(Game game, Ability source) { List netManas = new ArrayList<>(); - Mana types = getManaTypes(game, source); - if (types.getBlack() > 0) { - netManas.add(new Mana(ColoredManaSymbol.B)); - } - if (types.getRed() > 0) { - netManas.add(new Mana(ColoredManaSymbol.R)); - } - if (types.getBlue() > 0) { - netManas.add(new Mana(ColoredManaSymbol.U)); - } - if (types.getGreen() > 0) { - netManas.add(new Mana(ColoredManaSymbol.G)); - } - if (types.getWhite() > 0) { - netManas.add(new Mana(ColoredManaSymbol.W)); - } - if (!onlyColors && types.getColorless() > 0) { - netManas.add(Mana.ColorlessMana(1)); - } - if (types.getAny() > 0) { - netManas.add(Mana.AnyMana(1)); + if (game != null) { + Set manaTypes = getManaTypes(game, source); + if ((manaTypes.size() == 5 && !manaTypes.contains(ManaType.COLORLESS)) || manaTypes.size() == 6) { // GENERIC should never be returned from getManaTypes + netManas.add(Mana.AnyMana(1)); + } else { + if (manaTypes.contains(ManaType.BLACK)) { + netManas.add(Mana.BlackMana(1)); + } + if (manaTypes.contains(ManaType.RED)) { + netManas.add(Mana.RedMana(1)); + } + if (manaTypes.contains(ManaType.BLUE)) { + netManas.add(Mana.BlueMana(1)); + } + if (manaTypes.contains(ManaType.GREEN)) { + netManas.add(Mana.GreenMana(1)); + } + if (manaTypes.contains(ManaType.WHITE)) { + netManas.add(Mana.WhiteMana(1)); + } + } + if (!onlyColors && manaTypes.contains(ManaType.COLORLESS)) { + netManas.add(Mana.ColorlessMana(1)); + } } return netManas; } @@ -118,35 +134,28 @@ class AnyColorLandsProduceManaEffect extends ManaEffect { if (game == null) { return mana; } - Mana types = getManaTypes(game, source); + Set types = getManaTypes(game, source); Choice choice = new ChoiceColor(true); choice.getChoices().clear(); - choice.setMessage("Pick a mana color"); - if (types.getBlack() > 0) { + choice.setMessage("Pick a mana " + (onlyColors ? "color" : "type")); + if (types.contains(ManaType.BLACK)) { choice.getChoices().add("Black"); } - if (types.getRed() > 0) { + if (types.contains(ManaType.RED)) { choice.getChoices().add("Red"); } - if (types.getBlue() > 0) { + if (types.contains(ManaType.BLUE)) { choice.getChoices().add("Blue"); } - if (types.getGreen() > 0) { + if (types.contains(ManaType.GREEN)) { choice.getChoices().add("Green"); } - if (types.getWhite() > 0) { + if (types.contains(ManaType.WHITE)) { choice.getChoices().add("White"); } - if (!onlyColors && types.getColorless() > 0) { + if (types.contains(ManaType.COLORLESS)) { choice.getChoices().add("Colorless"); } - if (types.getAny() > 0) { // Only any Color - choice.getChoices().add("Black"); - choice.getChoices().add("Red"); - choice.getChoices().add("Blue"); - choice.getChoices().add("Green"); - choice.getChoices().add("White"); - } if (!choice.getChoices().isEmpty()) { Player player = game.getPlayer(source.getControllerId()); if (choice.getChoices().size() == 1) { @@ -182,24 +191,19 @@ class AnyColorLandsProduceManaEffect extends ManaEffect { return mana; } - private Mana getManaTypes(Game game, Ability source) { - Mana types = new Mana(); + private Set getManaTypes(Game game, Ability source) { + Set types = new HashSet<>(); if (game == null || game.getPhase() == null) { return types; } - if (inManaTypeCalculation) { + if (inManaTypeCalculation) { // Stop endless loops return types; } inManaTypeCalculation = true; List lands = game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source.getSourceId(), game); for (Permanent land : lands) { - Abilities mana = land.getAbilities().getActivatedManaAbilities(Zone.BATTLEFIELD); - for (ActivatedManaAbilityImpl ability : mana) { - if (!ability.getSourceId().equals(source.getSourceId()) && ability.definesMana(game)) { - for (Mana netMana : ability.getNetMana(game)) { - types.add(netMana); - } - } + if (!land.getId().equals(source.getSourceId())) { + types.addAll(AnyColorLandsProduceManaAbility.getManaTypesFromPermanent(land, game)); } } inManaTypeCalculation = false; @@ -210,4 +214,5 @@ class AnyColorLandsProduceManaEffect extends ManaEffect { public AnyColorLandsProduceManaEffect copy() { return new AnyColorLandsProduceManaEffect(this); } + } diff --git a/Mage/src/main/java/mage/abilities/mana/ManaAbility.java b/Mage/src/main/java/mage/abilities/mana/ManaAbility.java index d4d0de12ff..2db974fa80 100644 --- a/Mage/src/main/java/mage/abilities/mana/ManaAbility.java +++ b/Mage/src/main/java/mage/abilities/mana/ManaAbility.java @@ -6,7 +6,9 @@ package mage.abilities.mana; import java.util.List; +import java.util.Set; import mage.Mana; +import mage.constants.ManaType; import mage.game.Game; /** @@ -24,6 +26,18 @@ public interface ManaAbility { */ List getNetMana(Game game); + /** + * 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 + * applicable replacement effects. If the type of mana can’t be defined, + * there’s no type of mana that that permanent could produce. The "type" of + * mana is its color, or lack thereof (for colorless mana). + * + * @param game + * @return + */ + Set getProducableManaTypes(Game game); + /** * Used to check if the ability itself defines mana types it can produce. * diff --git a/Mage/src/main/java/mage/abilities/mana/ManaOptions.java b/Mage/src/main/java/mage/abilities/mana/ManaOptions.java index 4fae1febde..149a728158 100644 --- a/Mage/src/main/java/mage/abilities/mana/ManaOptions.java +++ b/Mage/src/main/java/mage/abilities/mana/ManaOptions.java @@ -94,7 +94,7 @@ public class ManaOptions extends ArrayList { this.clear(); for (Mana netMana : netManas) { for (Mana mana : copy) { - if (!hasTapCost(ability) || checkManaReplacementAndTriggeredMana(ability, game, netMana)) { + if (ability.hasTapCost() || checkManaReplacementAndTriggeredMana(ability, game, netMana)) { Mana newMana = new Mana(); newMana.add(mana); newMana.add(netMana); @@ -104,7 +104,7 @@ public class ManaOptions extends ArrayList { } } - private List> getSimulatedTriggeredManaFromPlayer(Game game, Ability ability) { + private static List> getSimulatedTriggeredManaFromPlayer(Game game, Ability ability) { Player player = game.getPlayer(ability.getControllerId()); List> newList = new ArrayList<>(); if (player != null) { @@ -124,7 +124,7 @@ public class ManaOptions extends ArrayList { * @return false if mana production was completely replaced */ private boolean checkManaReplacementAndTriggeredMana(Ability ability, Game game, Mana mana) { - if (hasTapCost(ability)) { + if (ability.hasTapCost()) { ManaEvent event = new ManaEvent(GameEvent.EventType.TAPPED_FOR_MANA, ability.getSourceId(), ability.getSourceId(), ability.getControllerId(), mana); if (game.replaceEvent(event)) { return false; @@ -137,15 +137,6 @@ public class ManaOptions extends ArrayList { return true; } - public boolean hasTapCost(Ability ability) { - for (Cost cost : ability.getCosts()) { - if (cost instanceof TapSourceCost) { - return true; - } - } - return false; - } - /** * This adds the mana the abilities can produce to the possible mana * variabtion. @@ -267,10 +258,10 @@ public class ManaOptions extends ArrayList { return wasUsable; } - private List getTriggeredManaVariations(Game game, Ability ability, Mana baseMana) { + public static List getTriggeredManaVariations(Game game, Ability ability, Mana baseMana) { List baseManaPlusTriggeredMana = new ArrayList<>(); baseManaPlusTriggeredMana.add(baseMana); - List> availableTriggeredManaList = getSimulatedTriggeredManaFromPlayer(game, ability); + List> availableTriggeredManaList = ManaOptions.getSimulatedTriggeredManaFromPlayer(game, ability); for (List availableTriggeredMana : availableTriggeredManaList) { if (availableTriggeredMana.size() == 1) { for (Mana prevMana : baseManaPlusTriggeredMana) { @@ -372,7 +363,7 @@ public class ManaOptions extends ArrayList { 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 : getPossiblePayCombinations(cost.getGeneric(), currentMana)) { + 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; @@ -420,7 +411,13 @@ public class ManaOptions extends ArrayList { return oldManaWasReplaced; } - private List getPossiblePayCombinations(int number, Mana manaAvailable) { + /** + * + * @param number of generic mana + * @param manaAvailable + * @return + */ + public static List getPossiblePayCombinations(int number, Mana manaAvailable) { List payCombinations = new ArrayList<>(); List payCombinationsStrings = new ArrayList<>(); if (manaAvailable.countColored() > 0) { @@ -439,28 +436,28 @@ public class ManaOptions extends ArrayList { manaToPayFrom.subtract(existingMana); if (manaToPayFrom.getBlack() > 0 && !payCombinationsStrings.contains(existingMana.toString() + Mana.BlackMana(1).toString())) { manaToPayFrom.subtract(Mana.BlackMana(1)); - addManaCombination(Mana.BlackMana(1), existingMana, payCombinations, payCombinationsStrings); + ManaOptions.addManaCombination(Mana.BlackMana(1), existingMana, payCombinations, payCombinationsStrings); } if (manaToPayFrom.getBlue() > 0 && !payCombinationsStrings.contains(existingMana.toString() + Mana.BlueMana(1).toString())) { manaToPayFrom.subtract(Mana.BlueMana(1)); - addManaCombination(Mana.BlueMana(1), existingMana, payCombinations, payCombinationsStrings); + ManaOptions.addManaCombination(Mana.BlueMana(1), existingMana, payCombinations, payCombinationsStrings); } if (manaToPayFrom.getGreen() > 0 && !payCombinationsStrings.contains(existingMana.toString() + Mana.GreenMana(1).toString())) { manaToPayFrom.subtract(Mana.GreenMana(1)); - addManaCombination(Mana.GreenMana(1), existingMana, payCombinations, payCombinationsStrings); + ManaOptions.addManaCombination(Mana.GreenMana(1), existingMana, payCombinations, payCombinationsStrings); } if (manaToPayFrom.getRed() > 0 && !payCombinationsStrings.contains(existingMana.toString() + Mana.RedMana(1).toString())) { manaToPayFrom.subtract(Mana.RedMana(1)); - addManaCombination(Mana.RedMana(1), existingMana, payCombinations, payCombinationsStrings); + ManaOptions.addManaCombination(Mana.RedMana(1), existingMana, payCombinations, payCombinationsStrings); } if (manaToPayFrom.getWhite() > 0 && !payCombinationsStrings.contains(existingMana.toString() + Mana.WhiteMana(1).toString())) { manaToPayFrom.subtract(Mana.WhiteMana(1)); - addManaCombination(Mana.WhiteMana(1), existingMana, payCombinations, payCombinationsStrings); + ManaOptions.addManaCombination(Mana.WhiteMana(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)); - addManaCombination(Mana.AnyMana(1), existingMana, payCombinations, payCombinationsStrings); + ManaOptions.addManaCombination(Mana.AnyMana(1), existingMana, payCombinations, payCombinationsStrings); } } } @@ -480,7 +477,7 @@ public class ManaOptions extends ArrayList { return false; } - private void addManaCombination(Mana mana, Mana existingMana, List payCombinations, List payCombinationsStrings) { + public static void addManaCombination(Mana mana, Mana existingMana, List payCombinations, List payCombinationsStrings) { Mana newMana = existingMana.copy(); newMana.add(mana); payCombinations.add(newMana); diff --git a/Mage/src/main/java/mage/abilities/mana/TriggeredManaAbility.java b/Mage/src/main/java/mage/abilities/mana/TriggeredManaAbility.java index abb6c7f838..7d3d424854 100644 --- a/Mage/src/main/java/mage/abilities/mana/TriggeredManaAbility.java +++ b/Mage/src/main/java/mage/abilities/mana/TriggeredManaAbility.java @@ -9,7 +9,10 @@ import mage.constants.Zone; import mage.game.Game; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import mage.constants.ManaType; /** * see 20110715 - 605.1b @@ -56,7 +59,18 @@ public abstract class TriggeredManaAbility extends TriggeredAbilityImpl implemen } return new ArrayList<>(netMana); } - + + @Override + public Set getProducableManaTypes(Game game) { + Set manaTypes = new HashSet<>(); + for (Effect effect : getEffects()) { + if (effect instanceof ManaEffect) { + manaTypes.addAll(((ManaEffect) effect).getProducableManaTypes(game, this)); + } + } + return manaTypes; + } + /** * Used to check if the ability itself defines mana types it can produce. * diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index d7e6e68066..8d388fddf2 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -2906,7 +2906,7 @@ public abstract class PlayerImpl implements Player, Serializable { } 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)) { + if (!ability.hasTapCost()) { it.remove(); Abilities noTapAbilities = new AbilitiesImpl<>(ability); if (ability.getManaCosts().isEmpty()) {