From e3db50f111ad795d8145fd456e2091410a8338a9 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Wed, 27 Jan 2021 10:13:10 +0400 Subject: [PATCH] * Double faces and adventure cards: improved support with some "exile and cast" effects like Hostage Taker (#7446); --- .../src/mage/cards/g/GontiLordOfLuxury.java | 12 +- Mage.Sets/src/mage/cards/i/IdentityThief.java | 2 +- .../src/mage/cards/r/RobberOfTheRich.java | 165 ++++-------------- .../src/mage/cards/s/StolenStrategy.java | 38 ---- .../src/mage/cards/v/ValkiGodOfLies.java | 6 +- .../cost/adventure/AdventureCardsTest.java | 54 ++++++ .../ModalDoubleFacesCardsTest.java | 40 +++++ .../CanPlayCardControllerEffect.java | 21 ++- ...SpendManaAsAnyColorToCastTargetEffect.java | 18 +- .../mage/game/permanent/PermanentCard.java | 9 +- .../mage/game/permanent/PermanentToken.java | 6 + Mage/src/main/java/mage/util/CardUtil.java | 20 ++- 12 files changed, 201 insertions(+), 190 deletions(-) diff --git a/Mage.Sets/src/mage/cards/g/GontiLordOfLuxury.java b/Mage.Sets/src/mage/cards/g/GontiLordOfLuxury.java index 8cb93d295be..47c54e24de3 100644 --- a/Mage.Sets/src/mage/cards/g/GontiLordOfLuxury.java +++ b/Mage.Sets/src/mage/cards/g/GontiLordOfLuxury.java @@ -188,11 +188,7 @@ class GontiLordOfLuxurySpendAnyManaEffect extends AsThoughEffectImpl implements if (theCard == null) { return false; } - Card mainCard = theCard.getMainCard(); - if (mainCard == null) { - return false; - } - objectId = mainCard.getId(); // for split cards + objectId = theCard.getMainCard().getId(); // for split cards if (objectId.equals(((FixedTarget) getTargetPointer()).getTarget()) && game.getState().getZoneChangeCounter(objectId) <= ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1) { // if the card moved from exile to spell the zone change counter is increased by 1 (effect must applies before and on stack, use isCheckPlayableMode?) @@ -237,11 +233,7 @@ class GontiLordOfLuxuryLookEffect extends AsThoughEffectImpl { if (theCard == null) { return false; } - Card mainCard = theCard.getMainCard(); - if (mainCard == null) { - return false; - } - objectId = mainCard.getId(); // for split cards + objectId = theCard.getMainCard().getId(); // for split cards if (affectedControllerId.equals(source.getControllerId()) && game.getState().getZone(objectId) == Zone.EXILED) { Player controller = game.getPlayer(source.getControllerId()); diff --git a/Mage.Sets/src/mage/cards/i/IdentityThief.java b/Mage.Sets/src/mage/cards/i/IdentityThief.java index 58208f9531a..a786d9c4b72 100644 --- a/Mage.Sets/src/mage/cards/i/IdentityThief.java +++ b/Mage.Sets/src/mage/cards/i/IdentityThief.java @@ -113,7 +113,7 @@ class IdentityThiefEffect extends OneShotEffect { if (controller != null && targetPermanent != null && sourcePermanent != null) { - ContinuousEffect copyEffect = new CopyEffect(Duration.EndOfTurn, targetPermanent.getMainCard(), source.getSourceId()); + ContinuousEffect copyEffect = new CopyEffect(Duration.EndOfTurn, targetPermanent, source.getSourceId()); copyEffect.setTargetPointer(new FixedTarget(sourcePermanent.getId())); game.addEffect(copyEffect, source); UUID exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); diff --git a/Mage.Sets/src/mage/cards/r/RobberOfTheRich.java b/Mage.Sets/src/mage/cards/r/RobberOfTheRich.java index cc3deb8a53a..e00f89878c5 100644 --- a/Mage.Sets/src/mage/cards/r/RobberOfTheRich.java +++ b/Mage.Sets/src/mage/cards/r/RobberOfTheRich.java @@ -8,8 +8,8 @@ import mage.abilities.condition.Condition; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.AsThoughManaEffect; -import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; +import mage.abilities.hint.ConditionHint; import mage.abilities.keyword.HasteAbility; import mage.abilities.keyword.ReachAbility; import mage.cards.Card; @@ -25,6 +25,7 @@ import mage.players.Player; import mage.target.targetpointer.FixedTarget; import mage.util.CardUtil; import mage.watchers.Watcher; +import mage.watchers.common.AttackedThisTurnWatcher; import java.util.HashSet; import java.util.Objects; @@ -52,14 +53,17 @@ public final class RobberOfTheRich extends CardImpl { this.addAbility(HasteAbility.getInstance()); // Whenever Robber of the Rich attacks, if defending player has more cards in hand than you, exile the top card of their library. During any turn you attacked with a Rogue, you may cast that card and you may spend mana as though it were mana of any color to cast that spell. - this.addAbility(new ConditionalInterveningIfTriggeredAbility( + Ability ability = new ConditionalInterveningIfTriggeredAbility( new AttacksTriggeredAbility( new RobberOfTheRichEffect(), false, "", SetTargetPointer.PLAYER - ), RobberOfTheRichCondition.instance, "Whenever {this} attacks, " + + ), RobberOfTheRichAttacksCondition.instance, "Whenever {this} attacks, " + "if defending player has more cards in hand than you, exile the top card of their library. " + "During any turn you attacked with a Rogue, you may cast that card and " + - "you may spend mana as though it were mana of any color to cast that spell." - ), new RobberOfTheRichWatcher()); + "you may spend mana as though it were mana of any color to cast that spell."); + ability.addWatcher(new AttackedThisTurnWatcher()); + ability.addHint(new ConditionHint(RobberOfTheRichAnyTurnAttackedCondition.instance)); + + this.addAbility(ability); } private RobberOfTheRich(final RobberOfTheRich card) { @@ -72,7 +76,7 @@ public final class RobberOfTheRich extends CardImpl { } } -enum RobberOfTheRichCondition implements Condition { +enum RobberOfTheRichAttacksCondition implements Condition { instance; @Override @@ -83,6 +87,30 @@ enum RobberOfTheRichCondition implements Condition { } } +enum RobberOfTheRichAnyTurnAttackedCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + // your turn + if (!source.isControlledBy(game.getActivePlayerId())) { + return false; + } + // attacked with Rogue + AttackedThisTurnWatcher watcher = game.getState().getWatcher(AttackedThisTurnWatcher.class); + return watcher != null && watcher.getAttackedThisTurnCreatures() + .stream() + .map(mor -> mor.getPermanentOrLKIBattlefield(game)) + .filter(Objects::nonNull) + .anyMatch(permanent -> permanent.hasSubtype(SubType.ROGUE, game)); + } + + @Override + public String toString() { + return "During that turn you attacked with a Rogue"; + } +} + class RobberOfTheRichEffect extends OneShotEffect { RobberOfTheRichEffect() { @@ -114,129 +142,12 @@ class RobberOfTheRichEffect extends OneShotEffect { // move card to exile controller.moveCardToExileWithInfo(card, exileId, sourceObject.getIdName(), source, game, Zone.LIBRARY, true); // Add effects only if the card has a spellAbility (e.g. not for lands). - if (card.getSpellAbility() == null) { - return true; + if (card.getSpellAbility() != null) { + // allow to cast the card + // and you may spend mana as though it were mana of any color to cast it + CardUtil.makeCardPlayableAndSpendManaAsAnyColor(game, source, card, Duration.Custom, RobberOfTheRichAnyTurnAttackedCondition.instance); } - // allow to cast the card - game.addEffect(new RobberOfTheRichCastFromExileEffect(card.getId(), exileId), source); - // and you may spend mana as though it were mana of any color to cast it - ContinuousEffect effect = new RobberOfTheRichSpendAnyManaEffect(); - effect.setTargetPointer(new FixedTarget(card.getId())); - game.addEffect(effect, source); return true; } } -class RobberOfTheRichCastFromExileEffect extends AsThoughEffectImpl { - - private UUID cardId; - private UUID exileId; - - RobberOfTheRichCastFromExileEffect(UUID cardId, UUID exileId) { - super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.Custom, Outcome.Benefit); - this.cardId = cardId; - this.exileId = exileId; - } - - private RobberOfTheRichCastFromExileEffect(final RobberOfTheRichCastFromExileEffect effect) { - super(effect); - this.cardId = effect.cardId; - this.exileId = effect.exileId; - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public RobberOfTheRichCastFromExileEffect copy() { - return new RobberOfTheRichCastFromExileEffect(this); - } - - @Override - public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) { - RobberOfTheRichWatcher watcher = game.getState().getWatcher(RobberOfTheRichWatcher.class); - if (watcher == null || !watcher.getAttackedWithRogue(source.getControllerId())) { - return false; - } - if (!sourceId.equals(cardId) || !source.isControlledBy(affectedControllerId)) { - return false; - } - ExileZone exileZone = game.getState().getExile().getExileZone(exileId); - if (exileZone != null && exileZone.contains(cardId)) { - return true; - } - discard(); - return false; - } -} - -class RobberOfTheRichSpendAnyManaEffect extends AsThoughEffectImpl implements AsThoughManaEffect { - - RobberOfTheRichSpendAnyManaEffect() { - super(AsThoughEffectType.SPEND_OTHER_MANA, Duration.Custom, Outcome.Benefit); - } - - private RobberOfTheRichSpendAnyManaEffect(final RobberOfTheRichSpendAnyManaEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public RobberOfTheRichSpendAnyManaEffect copy() { - return new RobberOfTheRichSpendAnyManaEffect(this); - } - - @Override - public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - objectId = CardUtil.getMainCardId(game, objectId); // for split cards - FixedTarget fixedTarget = ((FixedTarget) getTargetPointer()); - return source.isControlledBy(affectedControllerId) - && Objects.equals(objectId, fixedTarget.getTarget()) - && game.getState().getZoneChangeCounter(objectId) <= fixedTarget.getZoneChangeCounter() + 1 - && (game.getState().getZone(objectId) == Zone.STACK || game.getState().getZone(objectId) == Zone.EXILED); - } - - @Override - public ManaType getAsThoughManaType(ManaType manaType, ManaPoolItem mana, UUID affectedControllerId, Ability source, Game game) { - return mana.getFirstAvailable(); - } -} - -class RobberOfTheRichWatcher extends Watcher { - - private Set rogueAttackers = new HashSet(); - - RobberOfTheRichWatcher() { - super(WatcherScope.GAME); - } - - - @Override - public void watch(GameEvent event, Game game) { - if (event.getType() != GameEvent.EventType.ATTACKER_DECLARED) { - return; - } - Permanent permanent = game.getPermanent(event.getSourceId()); - if (permanent == null || !permanent.hasSubtype(SubType.ROGUE, game)) { - return; - } - rogueAttackers.add(event.getPlayerId()); - } - - @Override - public void reset() { - super.reset(); - rogueAttackers.clear(); - } - - boolean getAttackedWithRogue(UUID playerId) { - return rogueAttackers.contains(playerId); - } -} - diff --git a/Mage.Sets/src/mage/cards/s/StolenStrategy.java b/Mage.Sets/src/mage/cards/s/StolenStrategy.java index e873a04d6f7..aee0cff70c3 100644 --- a/Mage.Sets/src/mage/cards/s/StolenStrategy.java +++ b/Mage.Sets/src/mage/cards/s/StolenStrategy.java @@ -87,41 +87,3 @@ class StolenStrategyEffect extends OneShotEffect { return true; } } - -class StolenStrategyCastFromExileEffect extends AsThoughEffectImpl { - - private final UUID cardId; - private final UUID exileId; - - public StolenStrategyCastFromExileEffect(UUID cardId, UUID exileId) { - super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfTurn, Outcome.Benefit); - staticText = "Until end of turn, you may cast that card and you may spend mana as though it were mana of any color to cast it"; - this.cardId = cardId; - this.exileId = exileId; - } - - public StolenStrategyCastFromExileEffect(final StolenStrategyCastFromExileEffect effect) { - super(effect); - this.cardId = effect.cardId; - this.exileId = effect.exileId; - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public StolenStrategyCastFromExileEffect copy() { - return new StolenStrategyCastFromExileEffect(this); - } - - @Override - public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) { - if (sourceId.equals(cardId) && source.isControlledBy(affectedControllerId)) { - ExileZone exileZone = game.getState().getExile().getExileZone(exileId); - return exileZone != null && exileZone.contains(cardId); - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/v/ValkiGodOfLies.java b/Mage.Sets/src/mage/cards/v/ValkiGodOfLies.java index fa685adee4b..7942ebf9111 100644 --- a/Mage.Sets/src/mage/cards/v/ValkiGodOfLies.java +++ b/Mage.Sets/src/mage/cards/v/ValkiGodOfLies.java @@ -242,9 +242,9 @@ class ValkiGodOfLiesCopyExiledEffect extends OneShotEffect { Cards cards = game.getExile().getExileZone(exileId); if (!cards.isEmpty() && controller.choose(Outcome.Benefit, cards, target, game)) { - Card chosenExiledCreature = game.getCard(target.getFirstTarget()); - if (chosenExiledCreature != null) { - ContinuousEffect copyEffect = new CopyEffect(Duration.WhileOnBattlefield, chosenExiledCreature.getMainCard(), source.getSourceId()); + Card chosenExiledCard = game.getCard(target.getFirstTarget()); + if (chosenExiledCard != null) { + ContinuousEffect copyEffect = new CopyEffect(Duration.WhileOnBattlefield, chosenExiledCard.getMainCard(), source.getSourceId()); copyEffect.setTargetPointer(new FixedTarget(Valki.getId())); game.addEffect(copyEffect, source); return true; diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/adventure/AdventureCardsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/adventure/AdventureCardsTest.java index 92ba5af9a87..ecd470cc561 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/adventure/AdventureCardsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/adventure/AdventureCardsTest.java @@ -689,4 +689,58 @@ public class AdventureCardsTest extends CardTestPlayerBase { execute(); assertAllCommandsUsed(); } + + @Test + public void test_HostageTaker_CastFromExileAllParts() { + // bug: mdf must be playable as both sides + // https://github.com/magefree/mage/pull/7446 + + // Curious Pair {1}{G}, creature + // Treats to Share {G}, sorcery + // Create a Food token. + addCard(Zone.HAND, playerA, "Curious Pair"); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); // for prepare + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); // cast from hostage taker for any color + // + // When Hostage Taker enters the battlefield, exile another target artifact or creature until Hostage Taker + // leaves the battlefield. You may cast that card as long as it remains exiled, and you may spend mana + // as though it were mana of any type to cast that spell. + addCard(Zone.HAND, playerA, "Hostage Taker", 2); // {2}{U}{B} + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); + + // prepare adventure card on battlefield + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}", 2); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Curious Pair"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("prepare", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Curious Pair", 1); + + // turn 1 - exile by hostage + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Hostage Taker"); + addTarget(playerA, "Curious Pair"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkExileCount("after exile 1", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Curious Pair", 1); + // play as creature for any color + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Curious Pair"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("after play 1", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Curious Pair", 1); + + // eat green mana (test what hostage's any color effect work) + activateManaAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}", 2); + + // turn 3 - exile by hostage + castSpell(3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Hostage Taker"); + addTarget(playerA, "Curious Pair"); + waitStackResolved(3, PhaseStep.POSTCOMBAT_MAIN); + checkExileCount("after exile 3", 3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Curious Pair", 1); + // play as adventure spell + castSpell(3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Treats to Share"); + waitStackResolved(3, PhaseStep.POSTCOMBAT_MAIN); + checkPermanentCount("after play 3", 3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Food", 1); + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaces/ModalDoubleFacesCardsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaces/ModalDoubleFacesCardsTest.java index 74dcdeeb8eb..827f1db7cf0 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaces/ModalDoubleFacesCardsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaces/ModalDoubleFacesCardsTest.java @@ -696,6 +696,46 @@ public class ModalDoubleFacesCardsTest extends CardTestPlayerBase { assertPermanentCount(playerA, "Balduvian Bears", 2); } + @Test + public void test_Single_HostageTaker_CastFromExile() { + // bug: mdf must be playable as both sides + // https://github.com/magefree/mage/pull/7446 + + // Akoum Warrior {5}{R} - creature + // Akoum Teeth - land + addCard(Zone.HAND, playerA, "Akoum Warrior"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 6); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 6); // cast from hostage taker for any color + // + // When Hostage Taker enters the battlefield, exile another target artifact or creature until Hostage Taker + // leaves the battlefield. You may cast that card as long as it remains exiled, and you may spend mana + // as though it were mana of any type to cast that spell. + addCard(Zone.HAND, playerA, "Hostage Taker", 1); // {2}{U}{B} + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); + + // prepare mdf on battlefield + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}", 6); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akoum Warrior"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("prepare", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akoum Warrior", 1); + + // exile by hostage + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Hostage Taker"); + addTarget(playerA, "Akoum Warrior"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkExileCount("after exile", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akoum Warrior", 1); + // play as creature for any color + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akoum Warrior"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Akoum Warrior", 1); + } + @Test public void test_ETB_OnlySideCardsCanAddAbilitiesToGame() { // possible bug: double triggers (loadCard adds abilities from main + side cards instead side card only) diff --git a/Mage/src/main/java/mage/abilities/effects/common/asthought/CanPlayCardControllerEffect.java b/Mage/src/main/java/mage/abilities/effects/common/asthought/CanPlayCardControllerEffect.java index badff50258c..45f98d8790e 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/asthought/CanPlayCardControllerEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/asthought/CanPlayCardControllerEffect.java @@ -2,11 +2,13 @@ package mage.abilities.effects.common.asthought; import mage.MageObjectReference; import mage.abilities.Ability; +import mage.abilities.condition.Condition; import mage.abilities.effects.AsThoughEffectImpl; import mage.constants.AsThoughEffectType; import mage.constants.Duration; import mage.constants.Outcome; import mage.game.Game; +import mage.util.CardUtil; import java.util.UUID; @@ -14,22 +16,31 @@ import java.util.UUID; * Play card from current zone. Will be discarded on any card movements or blinks. *

* Recommends to use combo effects from CardUtil.makeCardPlayableAndSpendManaAsAnyColor instead signle effect + *

+ * Affected to all card's parts * * @author JayDi85 */ public class CanPlayCardControllerEffect extends AsThoughEffectImpl { - protected final MageObjectReference mor; + private final MageObjectReference mor; + private final Condition condition; public CanPlayCardControllerEffect(Game game, UUID cardId, int cardZCC, Duration duration) { + this(game, cardId, cardZCC, duration, null); + } + + public CanPlayCardControllerEffect(Game game, UUID cardId, int cardZCC, Duration duration, Condition condition) { super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, duration, Outcome.Benefit); this.staticText = "You may play those card"; this.mor = new MageObjectReference(cardId, cardZCC, game); + this.condition = condition; } public CanPlayCardControllerEffect(final CanPlayCardControllerEffect effect) { super(effect); this.mor = effect.mor; + this.condition = effect.condition; } @Override @@ -48,6 +59,12 @@ public class CanPlayCardControllerEffect extends AsThoughEffectImpl { discard(); return false; } - return mor.refersTo(sourceId, game) && source.isControlledBy(affectedControllerId); + + if (condition != null && !condition.apply(game, source)) { + return false; + } + + UUID objectIdToCast = CardUtil.getMainCardId(game, sourceId); // affected to all card's parts + return mor.refersTo(objectIdToCast, game) && source.isControlledBy(affectedControllerId); } } \ No newline at end of file diff --git a/Mage/src/main/java/mage/abilities/effects/common/asthought/YouMaySpendManaAsAnyColorToCastTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/asthought/YouMaySpendManaAsAnyColorToCastTargetEffect.java index 31be9b8ff4c..69aef22e597 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/asthought/YouMaySpendManaAsAnyColorToCastTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/asthought/YouMaySpendManaAsAnyColorToCastTargetEffect.java @@ -1,6 +1,7 @@ package mage.abilities.effects.common.asthought; import mage.abilities.Ability; +import mage.abilities.condition.Condition; import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.AsThoughManaEffect; import mage.constants.*; @@ -15,17 +16,27 @@ import java.util.UUID; /** * Spend mana as any color to cast targeted card. Will not affected after any card movements or blinks. * + * Affects to all card's parts + * * @author JayDi85 */ public class YouMaySpendManaAsAnyColorToCastTargetEffect extends AsThoughEffectImpl implements AsThoughManaEffect { + private final Condition condition; + public YouMaySpendManaAsAnyColorToCastTargetEffect(Duration duration) { + this(duration, null); + } + + public YouMaySpendManaAsAnyColorToCastTargetEffect(Duration duration, Condition condition) { super(AsThoughEffectType.SPEND_OTHER_MANA, duration, Outcome.Benefit); this.staticText = "You may spend mana as though it were mana of any color to cast it"; + this.condition = condition; } public YouMaySpendManaAsAnyColorToCastTargetEffect(final YouMaySpendManaAsAnyColorToCastTargetEffect effect) { super(effect); + this.condition = effect.condition; } @Override @@ -40,10 +51,15 @@ public class YouMaySpendManaAsAnyColorToCastTargetEffect extends AsThoughEffectI @Override public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + if (condition != null && !condition.apply(game, source)) { + return false; + } + objectId = CardUtil.getMainCardId(game, objectId); // for split cards FixedTarget fixedTarget = ((FixedTarget) getTargetPointer()); + UUID targetId = CardUtil.getMainCardId(game, fixedTarget.getTarget()); // Affects to all card's parts (example: Hostage Taker exile mdf card) return source.isControlledBy(affectedControllerId) - && Objects.equals(objectId, fixedTarget.getTarget()) + && Objects.equals(objectId, targetId) && game.getState().getZoneChangeCounter(objectId) <= fixedTarget.getZoneChangeCounter() + 1 && (game.getState().getZone(objectId) == Zone.STACK || game.getState().getZone(objectId) == Zone.EXILED); } diff --git a/Mage/src/main/java/mage/game/permanent/PermanentCard.java b/Mage/src/main/java/mage/game/permanent/PermanentCard.java index 3e5d5207d56..9952b2a2552 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentCard.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentCard.java @@ -10,6 +10,7 @@ import mage.cards.Card; import mage.cards.LevelerCard; import mage.game.Game; import mage.game.events.ZoneChangeEvent; +import mage.util.CardUtil; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; @@ -22,10 +23,8 @@ import java.util.UUID; public class PermanentCard extends PermanentImpl { protected int maxLevelCounters; - // A copy of the origin card that was cast (this is not the original card, so it's possible to chnage some attribute to this blueprint to change attributes to the permanent if it enters the battlefield with e.g. a subtype) + // A copy of the origin card that was cast (this is not the original card, so it's possible to change some attribute to this blueprint to change attributes to the permanent if it enters the battlefield with e.g. a subtype) protected Card card; - // A copy of original card that was used for copy and create current permanent (used in copy effects and special commands like adjustTargets) - protected Card copiedFromCard; // the number this permanent instance had protected int zoneChangeCounter; @@ -220,4 +219,8 @@ public class PermanentCard extends PermanentImpl { card.setZoneChangeCounter(value, game); } + @Override + public Card getMainCard() { + return card.getMainCard(); + } } diff --git a/Mage/src/main/java/mage/game/permanent/PermanentToken.java b/Mage/src/main/java/mage/game/permanent/PermanentToken.java index aed3e5f9d84..f18118fbe93 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentToken.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentToken.java @@ -3,6 +3,7 @@ package mage.game.permanent; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.costs.mana.ManaCost; +import mage.cards.Card; import mage.constants.EmptyNames; import mage.game.Game; import mage.game.permanent.token.Token; @@ -118,4 +119,9 @@ public class PermanentToken extends PermanentImpl { } } + @Override + public Card getMainCard() { + // token don't have game card, so return itself + return this; + } } diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index e944c4be845..3df0aeea716 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -13,6 +13,7 @@ import mage.abilities.Abilities; import mage.abilities.Ability; import mage.abilities.Mode; import mage.abilities.SpellAbility; +import mage.abilities.condition.Condition; import mage.abilities.costs.VariableCost; import mage.abilities.costs.mana.*; import mage.abilities.effects.ContinuousEffect; @@ -1024,21 +1025,30 @@ public final class CardUtil { } } + public static void makeCardPlayableAndSpendManaAsAnyColor(Game game, Ability source, Card card, Duration duration) { + makeCardPlayableAndSpendManaAsAnyColor(game, source, card, duration, null); + } + /** * Add effects to game that allows to play/cast card from current zone and spend mana as any type for it. * Effects will be discarded/ignored on any card movements or blinks (after ZCC change) * + * Affected to all card's parts + * * @param game * @param card * @param duration + * @param condition can be null */ - public static void makeCardPlayableAndSpendManaAsAnyColor(Game game, Ability source, Card card, Duration duration) { + public static void makeCardPlayableAndSpendManaAsAnyColor(Game game, Ability source, Card card, Duration duration, Condition condition) { // Effect can be used for cards in zones and permanents on battlefield - // So there is a workaround to get real ZCC (PermanentCard's ZCC is static, but it must be from Card's ZCC) + // PermanentCard's ZCC is static, but we need updated ZCC from the card (after moved to another zone) + // So there is a workaround to get actual card's ZCC // Example: Hostage Taker - int zcc = game.getState().getZoneChangeCounter(card.getId()); - game.addEffect(new CanPlayCardControllerEffect(game, card.getId(), zcc, duration), source); - game.addEffect(new YouMaySpendManaAsAnyColorToCastTargetEffect(duration).setTargetPointer(new FixedTarget(card.getId(), zcc)), source); + UUID objectId = card.getMainCard().getId(); + int zcc = game.getState().getZoneChangeCounter(objectId); + game.addEffect(new CanPlayCardControllerEffect(game, objectId, zcc, duration, condition), source); + game.addEffect(new YouMaySpendManaAsAnyColorToCastTargetEffect(duration, condition).setTargetPointer(new FixedTarget(objectId, zcc)), source); } /**