From b70638acc9d3114712cc3078753f801a8ee66649 Mon Sep 17 00:00:00 2001 From: ssk97 Date: Thu, 22 Aug 2024 13:33:39 -0700 Subject: [PATCH] Unbound Flourishing's X doubling should be a triggered ability (and related refactors) (#12597) Complete rework of Unbound Flourishing, removing the multiplier code for casting X spells. Adds ActivateAbilityTriggeredAbility, NotManaAbilityPredicate, AbilitySourceAttachedPredicate CopyStackObjectEffect now uses a MOR. OrTriggeredAbility now works with target pointer setting abilities. --- .../src/mage/player/ai/SimulatedPlayer2.java | 7 +- .../java/mage/player/ai/ComputerPlayer.java | 12 +- .../src/mage/player/human/HumanPlayer.java | 6 +- Mage.Sets/src/mage/cards/a/AbolethSpawn.java | 4 +- .../src/mage/cards/a/AshnodTheUncaring.java | 12 +- .../src/mage/cards/b/BattlemagesBracers.java | 61 ++---- .../mage/cards/b/BreechesTheBlastmaker.java | 3 +- .../src/mage/cards/c/ChandrasRegulator.java | 58 +----- .../mage/cards/d/DynaheirInvokerAdept.java | 10 +- .../mage/cards/e/ErthaJoFrontierMentor.java | 7 +- .../src/mage/cards/i/IllusionistsBracers.java | 62 ++----- .../src/mage/cards/k/KeralKeepDisciples.java | 6 +- .../mage/cards/k/KurkeshOnakkeAncient.java | 59 ++---- .../mage/cards/l/LeoriSparktouchedHunter.java | 10 +- .../mage/cards/l/LocusOfEnlightenment.java | 48 +---- .../src/mage/cards/m/MagusLuceaKane.java | 18 +- .../src/mage/cards/m/MirrorShieldHoplite.java | 3 +- .../src/mage/cards/r/RingsOfBrighthearth.java | 59 ++---- Mage.Sets/src/mage/cards/r/RowansTalent.java | 10 +- .../src/mage/cards/u/UnboundFlourishing.java | 175 ++++++------------ .../src/mage/cards/v/VerrakWarpedSengir.java | 3 +- .../continuous/UnboundFlourishingTest.java | 34 +++- .../java/org/mage/test/player/TestPlayer.java | 4 +- .../main/java/mage/abilities/AbilityImpl.java | 18 +- .../ActivateAbilityTriggeredAbility.java | 73 ++++++++ ...swalkerLoyaltyAbilityTriggeredAbility.java | 24 ++- .../abilities/costs/mana/ManaCostsImpl.java | 1 - .../effects/common/CopyStackObjectEffect.java | 19 +- .../abilities/meta/OrTriggeredAbility.java | 4 + .../other/AbilitySourceAttachedPredicate.java | 30 +++ .../other/NotManaAbilityPredicate.java | 26 +++ .../command/emblems/RowanKenrithEmblem.java | 55 ++---- .../main/java/mage/game/events/GameEvent.java | 7 - .../main/java/mage/game/stack/SpellStack.java | 10 +- Mage/src/main/java/mage/players/Player.java | 6 +- .../main/java/mage/players/StubPlayer.java | 2 +- 36 files changed, 399 insertions(+), 547 deletions(-) create mode 100644 Mage/src/main/java/mage/abilities/common/ActivateAbilityTriggeredAbility.java create mode 100644 Mage/src/main/java/mage/filter/predicate/other/AbilitySourceAttachedPredicate.java create mode 100644 Mage/src/main/java/mage/filter/predicate/other/NotManaAbilityPredicate.java diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/SimulatedPlayer2.java b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/SimulatedPlayer2.java index d02b446c38f..55113d1e99e 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/SimulatedPlayer2.java +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/SimulatedPlayer2.java @@ -2,7 +2,6 @@ package mage.player.ai; import mage.MageObject; import mage.abilities.Ability; -import mage.abilities.AbilityImpl; import mage.abilities.ActivatedAbility; import mage.abilities.TriggeredAbility; import mage.abilities.common.PassAbility; @@ -154,12 +153,8 @@ public final class SimulatedPlayer2 extends ComputerPlayer { } } // find real X value after replace events - int xMultiplier = 1; - if (newAbility instanceof AbilityImpl) { - xMultiplier = ((AbilityImpl) newAbility).handleManaXMultiplier(game, xMultiplier); - } newAbility.addManaCostsToPay(new ManaCostsImpl<>(new StringBuilder("{").append(xAnnounceValue).append('}').toString())); - newAbility.getManaCostsToPay().setX(xAnnounceValue * xMultiplier, xAnnounceValue * xInstancesCount); + newAbility.getManaCostsToPay().setX(xAnnounceValue, xAnnounceValue * xInstancesCount); if (varCost != null) { varCost.setPaid(); } diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java index 4170990cf4a..386e9a29c24 100644 --- a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java @@ -1921,20 +1921,18 @@ public class ComputerPlayer extends PlayerImpl { } @Override - public int announceXMana(int min, int max, int multiplier, String message, Game game, Ability ability) { + public int announceXMana(int min, int max, String message, Game game, Ability ability) { log.debug("announceXMana"); //TODO: improve this - int xMin = min * multiplier; - int xMax = (max == Integer.MAX_VALUE ? max : max * multiplier); int numAvailable = getAvailableManaProducers(game).size() - ability.getManaCosts().manaValue(); if (numAvailable < 0) { numAvailable = 0; } else { - if (numAvailable < xMin) { - numAvailable = xMin; + if (numAvailable < min) { + numAvailable = min; } - if (numAvailable > xMax) { - numAvailable = xMax; + if (numAvailable > max) { + numAvailable = max; } } return numAvailable; diff --git a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java index 24acd8c575f..a62a8baf24f 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java @@ -1668,24 +1668,22 @@ public class HumanPlayer extends PlayerImpl { * * @param min * @param max - * @param multiplier - X multiplier after replace events * @param message * @param ability * @param game * @return */ @Override - public int announceXMana(int min, int max, int multiplier, String message, Game game, Ability ability) { + public int announceXMana(int min, int max, String message, Game game, Ability ability) { if (gameInCheckPlayableState(game)) { return 0; } int xValue = 0; - String extraMessage = (multiplier == 1 ? "" : ", X will be increased by " + multiplier + " times"); while (canRespond()) { prepareForResponse(game); if (!isExecutingMacro()) { - game.fireGetAmountEvent(playerId, message + extraMessage + CardUtil.getSourceLogName(game, ability), min, max); + game.fireGetAmountEvent(playerId, message + CardUtil.getSourceLogName(game, ability), min, max); } waitForResponse(game); diff --git a/Mage.Sets/src/mage/cards/a/AbolethSpawn.java b/Mage.Sets/src/mage/cards/a/AbolethSpawn.java index 5b08b405703..539cf4f3c3f 100644 --- a/Mage.Sets/src/mage/cards/a/AbolethSpawn.java +++ b/Mage.Sets/src/mage/cards/a/AbolethSpawn.java @@ -17,6 +17,7 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.stack.StackObject; +import mage.target.targetpointer.FixedTarget; import java.util.UUID; @@ -92,8 +93,7 @@ class AbolethSpawnTriggeredAbility extends TriggeredAbilityImpl { if (triggerEvent.getSourceId() != permanent.getId()) { return false; // only triggered abilities of that creature } - // CopyStackObjectEffect needs value set - getEffects().setValue("stackObject", stackObject); + getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game)); return true; } diff --git a/Mage.Sets/src/mage/cards/a/AshnodTheUncaring.java b/Mage.Sets/src/mage/cards/a/AshnodTheUncaring.java index 528c951b2b8..23e78efa260 100644 --- a/Mage.Sets/src/mage/cards/a/AshnodTheUncaring.java +++ b/Mage.Sets/src/mage/cards/a/AshnodTheUncaring.java @@ -15,6 +15,7 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.stack.StackAbility; +import mage.target.targetpointer.FixedTarget; import java.util.UUID; @@ -53,6 +54,8 @@ class AshnodTheUncaringTriggeredAbility extends TriggeredAbilityImpl { AshnodTheUncaringTriggeredAbility() { super(Zone.BATTLEFIELD, new CopyStackObjectEffect(), true); + setTriggerPhrase("Whenever you activate an ability of an artifact or creature that isn't a mana ability, " + + "if one or more permanents were sacrificed to activate it, "); } private AshnodTheUncaringTriggeredAbility(final AshnodTheUncaringTriggeredAbility ability) { @@ -88,14 +91,7 @@ class AshnodTheUncaringTriggeredAbility extends TriggeredAbilityImpl { if (permanent == null || (!permanent.isArtifact(game) && !permanent.isCreature(game))) { return false; } - this.getEffects().setValue("stackObject", stackAbility); + getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game)); return true; } - - @Override - public String getRule() { - return "Whenever you activate an ability of an artifact or creature that isn't a mana ability, " + - "if one or more permanents were sacrificed to activate it, " + - "you may copy that ability. You may choose new targets for the copy."; - } } diff --git a/Mage.Sets/src/mage/cards/b/BattlemagesBracers.java b/Mage.Sets/src/mage/cards/b/BattlemagesBracers.java index 3bcd827b90c..01f7d0b1e29 100644 --- a/Mage.Sets/src/mage/cards/b/BattlemagesBracers.java +++ b/Mage.Sets/src/mage/cards/b/BattlemagesBracers.java @@ -1,6 +1,6 @@ package mage.cards.b; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.ActivateAbilityTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.common.CopyStackObjectEffect; @@ -11,10 +11,10 @@ import mage.abilities.keyword.HasteAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; -import mage.game.stack.StackAbility; +import mage.filter.FilterStackObject; +import mage.filter.common.FilterActivatedOrTriggeredAbility; +import mage.filter.predicate.other.AbilitySourceAttachedPredicate; +import mage.filter.predicate.other.NotManaAbilityPredicate; import mage.target.common.TargetControlledCreaturePermanent; import java.util.UUID; @@ -24,6 +24,13 @@ import java.util.UUID; */ public final class BattlemagesBracers extends CardImpl { + private static final FilterStackObject filter = new FilterActivatedOrTriggeredAbility("an ability of equipped creature"); + + static { + filter.add(NotManaAbilityPredicate.instance); + filter.add(AbilitySourceAttachedPredicate.instance); + } + public BattlemagesBracers(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{R}"); @@ -35,7 +42,8 @@ public final class BattlemagesBracers extends CardImpl { ))); // Whenever an ability of equipped creature is activated, if it isn't a mana ability, you may pay {1}. If you do, copy that ability. You may choose new targets for the copy. - this.addAbility(new BattlemagesBracersTriggeredAbility()); + this.addAbility(new ActivateAbilityTriggeredAbility(new DoIfCostPaid(new CopyStackObjectEffect(), new GenericManaCost(1)), filter, SetTargetPointer.SPELL) + .setTriggerPhrase("Whenever an ability of equipped creature is activated, if it isn't a mana ability, ")); // Equip {2} this.addAbility(new EquipAbility(Outcome.BoostCreature, new GenericManaCost(2), new TargetControlledCreaturePermanent(), false)); @@ -50,44 +58,3 @@ public final class BattlemagesBracers extends CardImpl { return new BattlemagesBracers(this); } } - -class BattlemagesBracersTriggeredAbility extends TriggeredAbilityImpl { - - BattlemagesBracersTriggeredAbility() { - super(Zone.BATTLEFIELD, new DoIfCostPaid(new CopyStackObjectEffect(), new GenericManaCost(1))); - } - - private BattlemagesBracersTriggeredAbility(final BattlemagesBracersTriggeredAbility ability) { - super(ability); - } - - @Override - public BattlemagesBracersTriggeredAbility copy() { - return new BattlemagesBracersTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ACTIVATED_ABILITY; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - Permanent equipment = game.getPermanent(this.getSourceId()); - if (equipment == null || !equipment.isAttachedTo(event.getSourceId())) { - return false; - } - StackAbility stackAbility = (StackAbility) game.getStack().getStackObject(event.getSourceId()); - if (stackAbility == null || stackAbility.getStackAbility().isManaActivatedAbility()) { - return false; - } - getEffects().setValue("stackObject", stackAbility); - return true; - } - - @Override - public String getRule() { - return "Whenever an ability of equipped creature is activated, if it isn't a mana ability, you may pay {1}. " + - "If you do, copy that ability. You may choose new targets for the copy."; - } -} diff --git a/Mage.Sets/src/mage/cards/b/BreechesTheBlastmaker.java b/Mage.Sets/src/mage/cards/b/BreechesTheBlastmaker.java index db5914c2712..e7e4e73bc89 100644 --- a/Mage.Sets/src/mage/cards/b/BreechesTheBlastmaker.java +++ b/Mage.Sets/src/mage/cards/b/BreechesTheBlastmaker.java @@ -22,6 +22,7 @@ import mage.game.Game; import mage.game.stack.Spell; import mage.players.Player; import mage.target.common.TargetAnyTarget; +import mage.target.targetpointer.FixedTarget; import java.util.Optional; import java.util.UUID; @@ -87,7 +88,7 @@ class BreechesTheBlastmakerEffect extends OneShotEffect { if (player.flipCoin(source, game, true)) { Effect effect = new CopyStackObjectEffect(); effect.setText("copy that spell. You may choose new targets for the copy"); - effect.setValue("stackObject", spell); + effect.setTargetPointer(new FixedTarget(spell.getId(), game)); ability = new ReflexiveTriggeredAbility(effect, false); } else { int mv = Optional diff --git a/Mage.Sets/src/mage/cards/c/ChandrasRegulator.java b/Mage.Sets/src/mage/cards/c/ChandrasRegulator.java index 4576633cf09..e8704e00efd 100644 --- a/Mage.Sets/src/mage/cards/c/ChandrasRegulator.java +++ b/Mage.Sets/src/mage/cards/c/ChandrasRegulator.java @@ -8,18 +8,18 @@ import mage.abilities.costs.common.DiscardTargetCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CopyStackObjectEffect; +import mage.abilities.effects.common.DoIfCostPaid; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.SetTargetPointer; +import mage.constants.SubType; +import mage.constants.SuperType; import mage.filter.FilterCard; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.ColorPredicate; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.game.stack.StackAbility; -import mage.players.Player; import mage.target.common.TargetCardInHand; import java.util.UUID; @@ -44,7 +44,9 @@ public final class ChandrasRegulator extends CardImpl { this.supertype.add(SuperType.LEGENDARY); // Whenever you activate a loyalty ability of a Chandra planeswalker, you may pay {1}. If you do, copy that ability. You may choose new targets for the copy. - this.addAbility(new ActivatePlaneswalkerLoyaltyAbilityTriggeredAbility(new ChandrasRegulatorEffect(), SubType.CHANDRA)); + this.addAbility(new ActivatePlaneswalkerLoyaltyAbilityTriggeredAbility( + new DoIfCostPaid(new CopyStackObjectEffect(), new ManaCostsImpl<>("{1}")), + SubType.CHANDRA, SetTargetPointer.SPELL)); // {1}, {T}, Discard a Mountain card or a red card: Draw a card. Ability ability = new SimpleActivatedAbility( @@ -64,45 +66,3 @@ public final class ChandrasRegulator extends CardImpl { return new ChandrasRegulator(this); } } - -class ChandrasRegulatorEffect extends OneShotEffect { - - ChandrasRegulatorEffect() { - super(Outcome.Benefit); - staticText = "you may pay {1}. If you do, copy that ability. You may choose new targets for the copy"; - } - - private ChandrasRegulatorEffect(final ChandrasRegulatorEffect effect) { - super(effect); - } - - @Override - public ChandrasRegulatorEffect copy() { - return new ChandrasRegulatorEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - ManaCostsImpl cost = new ManaCostsImpl<>("{1}"); - if (player == null) { - return false; - } - if (!cost.canPay(source, source, player.getId(), game) - || !player.chooseUse(Outcome.Benefit, "Pay " + cost.getText() + - "? If you do, copy that ability. You may choose new targets for the copy.", source, game)) { - return true; - } - if (!cost.pay(source, game, source, source.getControllerId(), false, null)) { - return true; - } - StackAbility ability = (StackAbility) getValue("stackObject"); - Player controller = game.getPlayer(source.getControllerId()); - Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); - if (ability == null || controller == null || sourcePermanent == null) { - return false; - } - ability.createCopyOnStack(game, source, source.getControllerId(), true); - return true; - } -} diff --git a/Mage.Sets/src/mage/cards/d/DynaheirInvokerAdept.java b/Mage.Sets/src/mage/cards/d/DynaheirInvokerAdept.java index 05e45b73d0b..a12ff4e143b 100644 --- a/Mage.Sets/src/mage/cards/d/DynaheirInvokerAdept.java +++ b/Mage.Sets/src/mage/cards/d/DynaheirInvokerAdept.java @@ -17,6 +17,7 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.stack.StackAbility; +import mage.target.targetpointer.FixedTarget; import mage.watchers.common.ManaPaidSourceWatcher; import java.util.UUID; @@ -91,6 +92,7 @@ class DynaheirInvokerAdeptTriggeredAbility extends DelayedTriggeredAbility { DynaheirInvokerAdeptTriggeredAbility() { super(new CopyStackObjectEffect(), Duration.EndOfTurn, true); + setTriggerPhrase("When you next activate an ability that isn't a mana ability this turn by spending four or more mana to activate it, "); } private DynaheirInvokerAdeptTriggeredAbility(final DynaheirInvokerAdeptTriggeredAbility ability) { @@ -118,13 +120,7 @@ class DynaheirInvokerAdeptTriggeredAbility extends DelayedTriggeredAbility { || ManaPaidSourceWatcher.getTotalPaid(stackAbility.getId(), game) < 4) { return false; } - this.getEffects().setValue("stackObject", stackAbility); + getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game)); return true; } - - @Override - public String getRule() { - return "When you next activate an ability that isn't a mana ability this turn by spending four or more mana to activate it, " + - "copy that ability. You may choose new targets for the copy."; - } } diff --git a/Mage.Sets/src/mage/cards/e/ErthaJoFrontierMentor.java b/Mage.Sets/src/mage/cards/e/ErthaJoFrontierMentor.java index cb1df4774ce..95355a20e38 100644 --- a/Mage.Sets/src/mage/cards/e/ErthaJoFrontierMentor.java +++ b/Mage.Sets/src/mage/cards/e/ErthaJoFrontierMentor.java @@ -14,15 +14,12 @@ import mage.constants.Zone; import mage.filter.FilterPlayer; import mage.filter.FilterStackObject; import mage.filter.StaticFilters; -import mage.filter.predicate.ObjectSourcePlayer; -import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.filter.predicate.mageobject.TargetsPermanentOrPlayerPredicate; -import mage.filter.predicate.mageobject.TargetsPermanentPredicate; -import mage.filter.predicate.mageobject.TargetsPlayerPredicate; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.token.MercenaryToken; import mage.game.stack.StackObject; +import mage.target.targetpointer.FixedTarget; import java.util.UUID; @@ -94,7 +91,7 @@ class ErthaJoFrontierMentorTriggeredAbility extends TriggeredAbilityImpl { return false; } // For the copy effect to find. - this.getEffects().setValue("stackObject", stackObject); + getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game)); return true; } } diff --git a/Mage.Sets/src/mage/cards/i/IllusionistsBracers.java b/Mage.Sets/src/mage/cards/i/IllusionistsBracers.java index 4ec88f18202..6b95f96914e 100644 --- a/Mage.Sets/src/mage/cards/i/IllusionistsBracers.java +++ b/Mage.Sets/src/mage/cards/i/IllusionistsBracers.java @@ -1,6 +1,6 @@ package mage.cards.i; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.ActivateAbilityTriggeredAbility; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.common.CopyStackObjectEffect; import mage.abilities.keyword.EquipAbility; @@ -8,12 +8,12 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; +import mage.constants.SetTargetPointer; import mage.constants.SubType; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; -import mage.game.stack.StackAbility; +import mage.filter.FilterStackObject; +import mage.filter.common.FilterActivatedOrTriggeredAbility; +import mage.filter.predicate.other.AbilitySourceAttachedPredicate; +import mage.filter.predicate.other.NotManaAbilityPredicate; import java.util.UUID; @@ -22,12 +22,19 @@ import java.util.UUID; */ public final class IllusionistsBracers extends CardImpl { + private static final FilterStackObject filter = new FilterActivatedOrTriggeredAbility("an ability of equipped creature"); + + static { + filter.add(NotManaAbilityPredicate.instance); + filter.add(AbilitySourceAttachedPredicate.instance); + } public IllusionistsBracers(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); this.subtype.add(SubType.EQUIPMENT); // Whenever an ability of equipped creature is activated, if it isn't a mana ability, copy that ability. You may choose new targets for the copy. - this.addAbility(new IllusionistsBracersTriggeredAbility()); + this.addAbility(new ActivateAbilityTriggeredAbility(new CopyStackObjectEffect(), filter, SetTargetPointer.SPELL) + .setTriggerPhrase("Whenever an ability of equipped creature is activated, if it isn't a mana ability, ")); // Equip 3 this.addAbility(new EquipAbility(Outcome.BoostCreature, new GenericManaCost(3), false)); @@ -42,44 +49,3 @@ public final class IllusionistsBracers extends CardImpl { return new IllusionistsBracers(this); } } - -class IllusionistsBracersTriggeredAbility extends TriggeredAbilityImpl { - - IllusionistsBracersTriggeredAbility() { - super(Zone.BATTLEFIELD, new CopyStackObjectEffect()); - } - - private IllusionistsBracersTriggeredAbility(final IllusionistsBracersTriggeredAbility ability) { - super(ability); - } - - @Override - public IllusionistsBracersTriggeredAbility copy() { - return new IllusionistsBracersTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ACTIVATED_ABILITY; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - Permanent equipment = game.getPermanent(this.getSourceId()); - if (equipment == null || !equipment.isAttachedTo(event.getSourceId())) { - return false; - } - StackAbility stackAbility = (StackAbility) game.getStack().getStackObject(event.getSourceId()); - if (stackAbility == null || stackAbility.getStackAbility().isManaActivatedAbility()) { - return false; - } - this.getEffects().setValue("stackObject", stackAbility); - return true; - } - - @Override - public String getRule() { - return "Whenever an ability of equipped creature is activated, if it isn't a mana ability, " + - "copy that ability. You may choose new targets for the copy."; - } -} diff --git a/Mage.Sets/src/mage/cards/k/KeralKeepDisciples.java b/Mage.Sets/src/mage/cards/k/KeralKeepDisciples.java index 83fbcf75b19..08764eb1980 100644 --- a/Mage.Sets/src/mage/cards/k/KeralKeepDisciples.java +++ b/Mage.Sets/src/mage/cards/k/KeralKeepDisciples.java @@ -6,6 +6,7 @@ import mage.abilities.effects.common.DamagePlayersEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.SetTargetPointer; import mage.constants.SubType; import mage.constants.TargetController; @@ -19,14 +20,15 @@ public final class KeralKeepDisciples extends CardImpl { public KeralKeepDisciples(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{R}"); - + this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.MONK); this.power = new MageInt(4); this.toughness = new MageInt(3); // Whenever you activate a loyalty ability of a Chandra planeswalker, Keral Keep Disciples deals 1 damage to each opponent. - this.addAbility(new ActivatePlaneswalkerLoyaltyAbilityTriggeredAbility(new DamagePlayersEffect(1, TargetController.OPPONENT), SubType.CHANDRA)); + this.addAbility(new ActivatePlaneswalkerLoyaltyAbilityTriggeredAbility( + new DamagePlayersEffect(1, TargetController.OPPONENT), SubType.CHANDRA, SetTargetPointer.NONE)); } private KeralKeepDisciples(final KeralKeepDisciples card) { diff --git a/Mage.Sets/src/mage/cards/k/KurkeshOnakkeAncient.java b/Mage.Sets/src/mage/cards/k/KurkeshOnakkeAncient.java index 429e87ab11f..e3ac25a7d6f 100644 --- a/Mage.Sets/src/mage/cards/k/KurkeshOnakkeAncient.java +++ b/Mage.Sets/src/mage/cards/k/KurkeshOnakkeAncient.java @@ -1,20 +1,20 @@ package mage.cards.k; import mage.MageInt; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.ActivateAbilityTriggeredAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.CopyStackObjectEffect; import mage.abilities.effects.common.DoIfCostPaid; -import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.SetTargetPointer; import mage.constants.SubType; import mage.constants.SuperType; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.stack.StackAbility; +import mage.filter.FilterStackObject; +import mage.filter.common.FilterActivatedOrTriggeredAbility; +import mage.filter.predicate.other.ArtifactSourcePredicate; +import mage.filter.predicate.other.NotManaAbilityPredicate; import java.util.UUID; @@ -22,6 +22,12 @@ import java.util.UUID; * @author emerald000 */ public final class KurkeshOnakkeAncient extends CardImpl { + private static final FilterStackObject filter = new FilterActivatedOrTriggeredAbility("an ability of an artifact, if it isn't a mana ability"); + + static { + filter.add(NotManaAbilityPredicate.instance); + filter.add(ArtifactSourcePredicate.instance); + } public KurkeshOnakkeAncient(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{R}"); @@ -33,7 +39,7 @@ public final class KurkeshOnakkeAncient extends CardImpl { this.toughness = new MageInt(3); // Whenever you activate an ability of an artifact, if it isn't a mana ability, you may pay {R}. If you do, copy that ability. You may choose new targets for the copy. - this.addAbility(new KurkeshOnakkeAncientTriggeredAbility()); + this.addAbility(new ActivateAbilityTriggeredAbility(new DoIfCostPaid(new CopyStackObjectEffect(), new ManaCostsImpl<>("{R}")), filter, SetTargetPointer.SPELL)); } private KurkeshOnakkeAncient(final KurkeshOnakkeAncient card) { @@ -45,42 +51,3 @@ public final class KurkeshOnakkeAncient extends CardImpl { return new KurkeshOnakkeAncient(this); } } - -class KurkeshOnakkeAncientTriggeredAbility extends TriggeredAbilityImpl { - - KurkeshOnakkeAncientTriggeredAbility() { - super(Zone.BATTLEFIELD, new DoIfCostPaid(new CopyStackObjectEffect(), new ManaCostsImpl<>("{R}"))); - setTriggerPhrase("Whenever you activate an ability of an artifact, if it isn't a mana ability, "); - } - - private KurkeshOnakkeAncientTriggeredAbility(final KurkeshOnakkeAncientTriggeredAbility ability) { - super(ability); - } - - @Override - public KurkeshOnakkeAncientTriggeredAbility copy() { - return new KurkeshOnakkeAncientTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ACTIVATED_ABILITY; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (!event.getPlayerId().equals(getControllerId())) { - return false; - } - Card source = game.getPermanentOrLKIBattlefield(event.getSourceId()); - if (source == null || !source.isArtifact(game)) { - return false; - } - StackAbility stackAbility = (StackAbility) game.getStack().getStackObject(event.getSourceId()); - if (stackAbility == null || stackAbility.getStackAbility().isManaActivatedAbility()) { - return false; - } - this.getEffects().setValue("stackObject", stackAbility); - return true; - } -} diff --git a/Mage.Sets/src/mage/cards/l/LeoriSparktouchedHunter.java b/Mage.Sets/src/mage/cards/l/LeoriSparktouchedHunter.java index 3fa9a07d849..f049f56bf9d 100644 --- a/Mage.Sets/src/mage/cards/l/LeoriSparktouchedHunter.java +++ b/Mage.Sets/src/mage/cards/l/LeoriSparktouchedHunter.java @@ -19,6 +19,7 @@ import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.stack.StackAbility; import mage.players.Player; +import mage.target.targetpointer.FixedTarget; import java.util.UUID; @@ -114,6 +115,7 @@ class LeoriSparktouchedHunterTriggeredAbility extends DelayedTriggeredAbility { super(new CopyStackObjectEffect(), Duration.EndOfTurn, false); this.subType = subType; this.addHint(new StaticHint("Chosen Subtype: " + subType)); + setTriggerPhrase("Whenever you activate an ability of a planeswalker of the chosen type, "); } private LeoriSparktouchedHunterTriggeredAbility(final LeoriSparktouchedHunterTriggeredAbility ability) { @@ -146,13 +148,7 @@ class LeoriSparktouchedHunterTriggeredAbility extends DelayedTriggeredAbility { if (permanent == null || !permanent.isPlaneswalker(game) || !permanent.hasSubtype(subType, game)) { return false; } - this.getEffects().setValue("stackObject", stackAbility); + getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game)); return true; } - - @Override - public String getRule() { - return "Whenever you activate an ability of a planeswalker of the chosen type, copy that ability. " + - "You may choose new targets for the copies."; - } } diff --git a/Mage.Sets/src/mage/cards/l/LocusOfEnlightenment.java b/Mage.Sets/src/mage/cards/l/LocusOfEnlightenment.java index 5f372a8e1dd..190c43c56fc 100644 --- a/Mage.Sets/src/mage/cards/l/LocusOfEnlightenment.java +++ b/Mage.Sets/src/mage/cards/l/LocusOfEnlightenment.java @@ -2,7 +2,7 @@ package mage.cards.l; import mage.abilities.Ability; import mage.abilities.ActivatedAbility; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.ActivateAbilityTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.common.CopyStackObjectEffect; @@ -10,11 +10,12 @@ import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; +import mage.filter.FilterStackObject; +import mage.filter.common.FilterActivatedOrTriggeredAbility; +import mage.filter.predicate.other.NotManaAbilityPredicate; import mage.game.ExileZone; import mage.game.Game; -import mage.game.events.GameEvent; import mage.game.permanent.Permanent; -import mage.game.stack.StackAbility; import mage.util.CardUtil; import java.util.UUID; @@ -23,7 +24,11 @@ import java.util.UUID; * @author TheElk801 */ public final class LocusOfEnlightenment extends CardImpl { + private static final FilterStackObject filter = new FilterActivatedOrTriggeredAbility("an ability that isn't a mana ability"); + static { + filter.add(NotManaAbilityPredicate.instance); + } public LocusOfEnlightenment(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, ""); @@ -35,7 +40,7 @@ public final class LocusOfEnlightenment extends CardImpl { this.addAbility(new SimpleStaticAbility(new LocusOfEnlightenmentEffect())); // Whenever you activate an ability that isn't a mana ability, copy it. You may choose new targets for the copy. - this.addAbility(new LocusOfEnlightenmentTriggeredAbility()); + this.addAbility(new ActivateAbilityTriggeredAbility(new CopyStackObjectEffect("it"), filter, SetTargetPointer.SPELL)); } private LocusOfEnlightenment(final LocusOfEnlightenment card) { @@ -91,38 +96,3 @@ class LocusOfEnlightenmentEffect extends ContinuousEffectImpl { return true; } } - -class LocusOfEnlightenmentTriggeredAbility extends TriggeredAbilityImpl { - - LocusOfEnlightenmentTriggeredAbility() { - super(Zone.BATTLEFIELD, new CopyStackObjectEffect().setText("copy it. You may choose new targets for the copy")); - this.setTriggerPhrase("Whenever you activate an ability that isn't a mana ability, "); - } - - private LocusOfEnlightenmentTriggeredAbility(final LocusOfEnlightenmentTriggeredAbility ability) { - super(ability); - } - - @Override - public LocusOfEnlightenmentTriggeredAbility copy() { - return new LocusOfEnlightenmentTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ACTIVATED_ABILITY; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (!event.getPlayerId().equals(getControllerId())) { - return false; - } - StackAbility stackAbility = (StackAbility) game.getStack().getStackObject(event.getSourceId()); - if (stackAbility == null || stackAbility.isManaAbility()) { - return false; - } - this.getEffects().setValue("stackObject", stackAbility); - return true; - } -} diff --git a/Mage.Sets/src/mage/cards/m/MagusLuceaKane.java b/Mage.Sets/src/mage/cards/m/MagusLuceaKane.java index b2467209cea..b7491e3eaaa 100644 --- a/Mage.Sets/src/mage/cards/m/MagusLuceaKane.java +++ b/Mage.Sets/src/mage/cards/m/MagusLuceaKane.java @@ -18,8 +18,8 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.stack.Spell; import mage.game.stack.StackAbility; -import mage.game.stack.StackObject; import mage.target.common.TargetCreaturePermanent; +import mage.target.targetpointer.FixedTarget; import java.util.UUID; @@ -65,7 +65,9 @@ public final class MagusLuceaKane extends CardImpl { class MagusLuceaKaneTriggeredAbility extends DelayedTriggeredAbility { MagusLuceaKaneTriggeredAbility() { - super(new CopyStackObjectEffect(), Duration.EndOfTurn, true, false); + super(new CopyStackObjectEffect("that spell or ability"), Duration.EndOfTurn, true, false); + setTriggerPhrase("When you next cast a spell with {X} in its mana cost " + + "or activate an ability with {X} in its activation cost this turn, "); } private MagusLuceaKaneTriggeredAbility(final MagusLuceaKaneTriggeredAbility ability) { @@ -92,9 +94,9 @@ class MagusLuceaKaneTriggeredAbility extends DelayedTriggeredAbility { // activated ability if (event.getType() == GameEvent.EventType.ACTIVATED_ABILITY) { StackAbility stackAbility = (StackAbility) game.getStack().getStackObject(event.getSourceId()); - if (stackAbility != null && !stackAbility.getStackAbility().isManaActivatedAbility()) { + if (stackAbility != null) { if (stackAbility.getManaCostsToPay().containsX()) { - this.getEffects().setValue("stackObject", (StackObject) stackAbility); + getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game)); return true; } } @@ -104,16 +106,10 @@ class MagusLuceaKaneTriggeredAbility extends DelayedTriggeredAbility { if (event.getType() == GameEvent.EventType.SPELL_CAST) { Spell spell = game.getStack().getSpell(event.getTargetId()); if (spell != null && spell.getSpellAbility().getManaCostsToPay().containsX()) { - this.getEffects().setValue("stackObject", (StackObject) spell); + getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game)); return true; } } return false; } - - @Override - public String getRule() { - return "When you next cast a spell with {X} in its mana cost or activate an ability with {X} in its " - + "activation cost this turn, copy that spell or ability. You may choose new targets for the copy."; - } } diff --git a/Mage.Sets/src/mage/cards/m/MirrorShieldHoplite.java b/Mage.Sets/src/mage/cards/m/MirrorShieldHoplite.java index b58e6faf088..5df74740f10 100644 --- a/Mage.Sets/src/mage/cards/m/MirrorShieldHoplite.java +++ b/Mage.Sets/src/mage/cards/m/MirrorShieldHoplite.java @@ -14,6 +14,7 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.stack.StackObject; +import mage.target.targetpointer.FixedTarget; import java.util.UUID; @@ -79,7 +80,7 @@ class MirrorShieldHopliteTriggeredAbility extends TriggeredAbilityImpl { if (permanent == null || !permanent.isCreature(game) || !permanent.isControlledBy(this.getControllerId())) { return false; } - this.getEffects().setValue("stackObject", sourceObject); + getEffects().setTargetPointer(new FixedTarget(event.getSourceId(), game)); return true; } } diff --git a/Mage.Sets/src/mage/cards/r/RingsOfBrighthearth.java b/Mage.Sets/src/mage/cards/r/RingsOfBrighthearth.java index 3ae8bb250e6..0ef7e3dde11 100644 --- a/Mage.Sets/src/mage/cards/r/RingsOfBrighthearth.java +++ b/Mage.Sets/src/mage/cards/r/RingsOfBrighthearth.java @@ -1,16 +1,16 @@ package mage.cards.r; -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.common.ActivateAbilityTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.CopyStackObjectEffect; import mage.abilities.effects.common.DoIfCostPaid; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.stack.StackAbility; +import mage.constants.SetTargetPointer; +import mage.filter.FilterStackObject; +import mage.filter.common.FilterActivatedOrTriggeredAbility; +import mage.filter.predicate.other.NotManaAbilityPredicate; import java.util.UUID; @@ -18,12 +18,17 @@ import java.util.UUID; * @author LevelX2 */ public final class RingsOfBrighthearth extends CardImpl { + private static final FilterStackObject filter = new FilterActivatedOrTriggeredAbility("an ability, if it isn't a mana ability"); + + static { + filter.add(NotManaAbilityPredicate.instance); + } public RingsOfBrighthearth(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); // Whenever you activate an ability, if it isn't a mana ability, you may pay {2}. If you do, copy that ability. You may choose new targets for the copy. - this.addAbility(new RingsOfBrighthearthTriggeredAbility()); + this.addAbility(new ActivateAbilityTriggeredAbility(new DoIfCostPaid(new CopyStackObjectEffect(), new ManaCostsImpl<>("{2}")), filter, SetTargetPointer.SPELL)); } private RingsOfBrighthearth(final RingsOfBrighthearth card) { @@ -35,43 +40,3 @@ public final class RingsOfBrighthearth extends CardImpl { return new RingsOfBrighthearth(this); } } - -class RingsOfBrighthearthTriggeredAbility extends TriggeredAbilityImpl { - - RingsOfBrighthearthTriggeredAbility() { - super(Zone.BATTLEFIELD, new DoIfCostPaid(new CopyStackObjectEffect(), new GenericManaCost(2))); - } - - private RingsOfBrighthearthTriggeredAbility(final RingsOfBrighthearthTriggeredAbility ability) { - super(ability); - } - - @Override - public RingsOfBrighthearthTriggeredAbility copy() { - return new RingsOfBrighthearthTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ACTIVATED_ABILITY; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (!event.getPlayerId().equals(getControllerId())) { - return false; - } - StackAbility stackAbility = (StackAbility) game.getStack().getStackObject(event.getSourceId()); - if (stackAbility == null || stackAbility.getStackAbility().isManaActivatedAbility()) { - return false; - } - this.getEffects().setValue("stackObject", stackAbility); - return true; - } - - @Override - public String getRule() { - return "Whenever you activate an ability, if it isn't a mana ability, you may pay {2}. " + - "If you do, copy that ability. You may choose new targets for the copy."; - } -} diff --git a/Mage.Sets/src/mage/cards/r/RowansTalent.java b/Mage.Sets/src/mage/cards/r/RowansTalent.java index fa0ce6a25f0..c7ba80df1e5 100644 --- a/Mage.Sets/src/mage/cards/r/RowansTalent.java +++ b/Mage.Sets/src/mage/cards/r/RowansTalent.java @@ -22,6 +22,7 @@ import mage.game.stack.StackObject; import mage.target.TargetPermanent; import mage.target.common.TargetCreaturePermanent; import mage.target.common.TargetPlaneswalkerPermanent; +import mage.target.targetpointer.FixedTarget; import java.util.UUID; @@ -72,6 +73,7 @@ class RowansTalentTriggeredAbility extends TriggeredAbilityImpl { RowansTalentTriggeredAbility() { super(Zone.BATTLEFIELD, new CopyStackObjectEffect()); + setTriggerPhrase("Whenever you activate a loyalty ability of enchanted planeswalker, "); } private RowansTalentTriggeredAbility(final RowansTalentTriggeredAbility ability) { @@ -100,13 +102,7 @@ class RowansTalentTriggeredAbility extends TriggeredAbilityImpl { if (stackObject == null || !(stackObject.getStackAbility() instanceof LoyaltyAbility)) { return false; } - this.getEffects().setValue("stackObject", stackObject); + getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game)); return true; } - - @Override - public String getRule() { - return "Whenever you activate a loyalty ability of enchanted planeswalker, " + - "copy that ability. You may choose new targets for the copy."; - } } diff --git a/Mage.Sets/src/mage/cards/u/UnboundFlourishing.java b/Mage.Sets/src/mage/cards/u/UnboundFlourishing.java index b6e7241d0c6..c91ae0e4b7b 100644 --- a/Mage.Sets/src/mage/cards/u/UnboundFlourishing.java +++ b/Mage.Sets/src/mage/cards/u/UnboundFlourishing.java @@ -1,24 +1,31 @@ package mage.cards.u; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.ActivateAbilityTriggeredAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.CopyStackObjectEffect; +import mage.abilities.meta.OrTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Duration; import mage.constants.Outcome; +import mage.constants.SetTargetPointer; import mage.constants.Zone; +import mage.filter.FilterSpell; +import mage.filter.FilterStackObject; +import mage.filter.common.FilterActivatedOrTriggeredAbility; +import mage.filter.common.FilterInstantOrSorcerySpell; +import mage.filter.predicate.Predicate; +import mage.filter.predicate.mageobject.PermanentPredicate; import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; import mage.game.stack.Spell; import mage.game.stack.StackAbility; +import mage.game.stack.StackObject; import mage.players.Player; import mage.util.CardUtil; +import java.util.Map; import java.util.UUID; /** @@ -26,18 +33,33 @@ import java.util.UUID; */ public final class UnboundFlourishing extends CardImpl { - final static String needPrefix = "_UnboundFlourishing_NeedCopy"; + private static final FilterSpell filterPermanent = new FilterSpell("a permanent spell with a mana cost that contains {X}"); + private static final FilterSpell filterInstantSorcery = new FilterInstantOrSorcerySpell("an instant or sorcery spell with a mana cost that contains {X}"); + private static final FilterStackObject filterAbility = new FilterActivatedOrTriggeredAbility("an activated ability with an activation cost that contains {X}"); + + static { + filterPermanent.add(PermanentPredicate.instance); + filterPermanent.add(UnboundFlourishingCostContainsXPredicate.instance); + filterInstantSorcery.add(UnboundFlourishingCostContainsXPredicate.instance); + filterAbility.add(UnboundFlourishingCostContainsXPredicate.instance); + } public UnboundFlourishing(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{G}"); // Whenever you cast a permanent spell with a mana cost that contains {X}, double the value of X. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new UnboundFlourishingDoubleXEffect())); + this.addAbility(new SpellCastControllerTriggeredAbility(new UnboundFlourishingDoubleXEffect(), filterPermanent, false, SetTargetPointer.SPELL)); // Whenever you cast an instant or sorcery spell or activate an ability, // if that spell’s mana cost or that ability’s activation cost contains {X}, copy that spell or ability. // You may choose new targets for the copy. - this.addAbility(new UnboundFlourishingCopyAbility()); + this.addAbility(new OrTriggeredAbility(Zone.BATTLEFIELD, + new CopyStackObjectEffect("that spell or ability"), false, + "Whenever you cast an instant or sorcery spell or activate an ability, " + + "if that spell's mana cost or that ability's activation cost contains {X}, ", + new SpellCastControllerTriggeredAbility(null, filterInstantSorcery, false, SetTargetPointer.SPELL), + new ActivateAbilityTriggeredAbility(null, filterAbility, SetTargetPointer.SPELL) + )); } private UnboundFlourishing(final UnboundFlourishing card) { @@ -50,130 +72,53 @@ public final class UnboundFlourishing extends CardImpl { } } -class UnboundFlourishingDoubleXEffect extends ReplacementEffectImpl { +enum UnboundFlourishingCostContainsXPredicate implements Predicate { + instance; + + @Override + public boolean apply(StackObject input, Game game) { + if (input instanceof Spell) { + return ((Spell) input).getSpellAbility().getManaCostsToPay().containsX(); + } else if (input instanceof StackAbility) { + return input.getStackAbility().getManaCostsToPay().containsX(); + } else { + return false; + } + } + + @Override + public String toString() { + return "Contains {X}"; + } +} + +class UnboundFlourishingDoubleXEffect extends OneShotEffect { UnboundFlourishingDoubleXEffect() { - super(Duration.WhileOnBattlefield, Outcome.Benefit, false); - staticText = "Whenever you cast a permanent spell with a mana cost that contains {X}, double the value of X"; + super(Outcome.Benefit); + this.staticText = "double the value of X"; } private UnboundFlourishingDoubleXEffect(final UnboundFlourishingDoubleXEffect effect) { super(effect); } - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - event.setAmount(CardUtil.overflowMultiply(event.getAmount(), 2)); - return false; - } - - @Override - public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.X_MANA_ANNOUNCE; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - Spell spell = game.getSpell(event.getTargetId()); - return spell != null && spell.isPermanent(game) && spell.isControlledBy(source.getControllerId()); - } - @Override public UnboundFlourishingDoubleXEffect copy() { return new UnboundFlourishingDoubleXEffect(this); } -} - -class UnboundFlourishingCopyAbility extends TriggeredAbilityImpl { - - UnboundFlourishingCopyAbility() { - super(Zone.BATTLEFIELD, new UnboundFlourishingCopyEffect(), false); - setTriggerPhrase("Whenever you cast an instant or sorcery spell or activate an ability, " + - "if that spell's mana cost or that ability's activation cost contains {X}"); - } - - private UnboundFlourishingCopyAbility(final UnboundFlourishingCopyAbility ability) { - super(ability); - } - - @Override - public UnboundFlourishingCopyAbility copy() { - return new UnboundFlourishingCopyAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ACTIVATED_ABILITY - || event.getType() == GameEvent.EventType.SPELL_CAST; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (!event.getPlayerId().equals(getControllerId())) { - return false; - } - - // activated ability - if (event.getType() == GameEvent.EventType.ACTIVATED_ABILITY) { - StackAbility stackAbility = (StackAbility) game.getStack().getStackObject(event.getSourceId()); - if (stackAbility != null && !stackAbility.getStackAbility().isManaActivatedAbility()) { - if (stackAbility.getManaCostsToPay().containsX()) { - game.getState().setValue(this.getSourceId() + UnboundFlourishing.needPrefix, stackAbility); - return true; - } - } - } - - // spell - if (event.getType() == GameEvent.EventType.SPELL_CAST) { - Spell spell = game.getStack().getSpell(event.getTargetId()); - if (spell != null && spell.isInstantOrSorcery(game)) { - if (spell.getSpellAbility().getManaCostsToPay().containsX()) { - game.getState().setValue(this.getSourceId() + UnboundFlourishing.needPrefix, spell); - return true; - } - } - } - return false; - } -} - -class UnboundFlourishingCopyEffect extends OneShotEffect { - - UnboundFlourishingCopyEffect() { - super(Outcome.Benefit); - this.staticText = ", copy that spell or ability. You may choose new targets for the copy"; - } - - private UnboundFlourishingCopyEffect(final UnboundFlourishingCopyEffect effect) { - super(effect); - } - - @Override - public UnboundFlourishingCopyEffect copy() { - return new UnboundFlourishingCopyEffect(this); - } @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); Player controller = game.getPlayer(source.getControllerId()); - Permanent sourcePermanent = game.getPermanent(source.getSourceId()); if (player != null && controller != null) { - Object needObject = game.getState().getValue(source.getSourceId() + UnboundFlourishing.needPrefix); - - // copy ability - if (needObject instanceof StackAbility) { - StackAbility stackAbility = (StackAbility) needObject; - stackAbility.createCopyOnStack(game, source, source.getControllerId(), true); - return true; - } - - // copy spell - if (needObject instanceof Spell) { - Spell spell = (Spell) needObject; - spell.createCopyOnStack(game, source, source.getControllerId(), true); - return true; + Spell needObject = game.getSpell(getTargetPointer().getFirst(game, source)); + if (needObject != null) { + Map tagsMap = CardUtil.getSourceCostsTagsMap(game, needObject.getSpellAbility()); + if (tagsMap.containsKey("X")) { + tagsMap.put("X", ((int) tagsMap.get("X")) * 2); + } } } return false; diff --git a/Mage.Sets/src/mage/cards/v/VerrakWarpedSengir.java b/Mage.Sets/src/mage/cards/v/VerrakWarpedSengir.java index 017ff3f724a..a16494cce81 100644 --- a/Mage.Sets/src/mage/cards/v/VerrakWarpedSengir.java +++ b/Mage.Sets/src/mage/cards/v/VerrakWarpedSengir.java @@ -17,6 +17,7 @@ import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.stack.StackAbility; +import mage.target.targetpointer.FixedTarget; import mage.util.CardUtil; import java.util.UUID; @@ -96,7 +97,7 @@ class VerrakWarpedSengirTriggeredAbility extends TriggeredAbilityImpl { if (lifePaid > 0) { this.getEffects().clear(); this.addEffect(new DoIfCostPaid(new CopyStackObjectEffect(), new PayLifeCost(lifePaid))); - this.getEffects().setValue("stackObject", stackAbility); + this.getEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game)); return true; } return false; diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/UnboundFlourishingTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/UnboundFlourishingTest.java index be56cc73bd6..eb67cfdf475 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/UnboundFlourishingTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/UnboundFlourishingTest.java @@ -47,10 +47,10 @@ public class UnboundFlourishingTest extends CardTestPlayerBase { addCard(Zone.HAND, playerA, "Endless One", 1); // {X} addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); - // cast with X=3, but double it + // cast with X=3, but double it twice castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Endless One"); - setChoice(playerA, "Unbound Flourishing"); // choose replacement effects setChoice(playerA, "X=3"); + setChoice(playerA, ""); //stack triggers checkPermanentCounters("after", 1, PhaseStep.BEGIN_COMBAT, playerA, "Endless One", CounterType.P1P1, 3 * 2 * 2); setStrictChooseMode(true); @@ -163,6 +163,36 @@ public class UnboundFlourishingTest extends CardTestPlayerBase { execute(); } + @Test + public void test_OnActivatedAbility_MustCopy2Counter() { + addCard(Zone.BATTLEFIELD, playerA, "Unbound Flourishing", 1); + // + // {X}{R}, {T}, Sacrifice Cinder Elemental: It deals X damage to any target. + addCard(Zone.BATTLEFIELD, playerA, "Cinder Elemental", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); + // + // + addCard(Zone.BATTLEFIELD, playerB, "Island", 1); + addCard(Zone.HAND, playerB, "Stifle", 1); + + // activate with X=3 and make copy with another target, not double X + checkLife("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 20); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{X}{R}", playerA); + setChoice(playerA, "X=3"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Stifle"); //Counter the original ability, copy must still work + addTarget(playerB, "stack ability ({X}{R}, {T}, Sacrifice"); + + setChoice(playerA, true); // change target + addTarget(playerA, playerB); // change to B + checkLife("after", 1, PhaseStep.BEGIN_COMBAT, playerA, 20); // original damage is countered + checkLife("after", 1, PhaseStep.BEGIN_COMBAT, playerB, 20 - 3); // copy damage + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + } + @Test public void test_VariableManaCost() { // VariableManaCost contains: diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index b90daabccfb..7983c3b14d3 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -2816,7 +2816,7 @@ public class TestPlayer implements Player { } @Override - public int announceXMana(int min, int max, int multiplier, String message, Game game, Ability ability) { + public int announceXMana(int min, int max, String message, Game game, Ability ability) { assertAliasSupportInChoices(false); if (!choices.isEmpty()) { for (String choice : choices) { @@ -2830,7 +2830,7 @@ public class TestPlayer implements Player { this.chooseStrictModeFailed("choice", game, getInfo(ability, game) + "\nMessage: " + message); - return computerPlayer.announceXMana(min, max, multiplier, message, game, ability); + return computerPlayer.announceXMana(min, max, message, game, ability); } @Override diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index 00e2e6d2b9d..6da0269961c 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -278,8 +278,6 @@ public abstract class AbilityImpl implements Ability { int xValue = CardUtil.getSourceCostsTag(game, this, "X", 0); this.clearManaCostsToPay(); VariableManaCost xCosts = new VariableManaCost(VariableCostType.ADDITIONAL); - // no x events - rules from Unbound Flourishing: - // - Spells with additional costs that include X won't be affected by Unbound Flourishing. X must be in the spell's mana cost. xCosts.setAmount(xValue, xValue, false); addManaCostsToPay(xCosts); } else { @@ -617,8 +615,6 @@ public abstract class AbilityImpl implements Ability { Cost fixedCost = variableCost.getFixedCostsFromAnnouncedValue(xValue); addCost(fixedCost); // set the xcosts to paid - // no x events - rules from Unbound Flourishing: - // - Spells with additional costs that include X won't be affected by Unbound Flourishing. X must be in the spell's mana cost. variableCost.setAmount(xValue, xValue, false); ((Cost) variableCost).setPaid(); String message = controller.getLogName() + " announces a value of " + xValue + " (" + variableCost.getActionText() + ')' @@ -653,13 +649,6 @@ public abstract class AbilityImpl implements Ability { } } - public int handleManaXMultiplier(Game game, int value) { - // some spells can change X value without new pays (Unbound Flourishing doubles X) - GameEvent xEvent = GameEvent.getEvent(GameEvent.EventType.X_MANA_ANNOUNCE, this.getId(), this, getControllerId(), value); - game.replaceEvent(xEvent, this); - return xEvent.getAmount(); - } - /** * Handles X mana costs and sets manaCostsToPay. * @@ -695,9 +684,8 @@ public abstract class AbilityImpl implements Ability { if (variableManaCost != null) { if (!variableManaCost.isPaid()) { // should only happen for human players int xValue; - int xValueMultiplier = handleManaXMultiplier(game, 1); if (!noMana || variableManaCost.getCostType().canUseAnnounceOnFreeCast()) { - xValue = controller.announceXMana(variableManaCost.getMinX(), variableManaCost.getMaxX(), xValueMultiplier, + xValue = controller.announceXMana(variableManaCost.getMinX(), variableManaCost.getMaxX(), "Announce the value for " + variableManaCost.getText(), game, this); int amountMana = xValue * variableManaCost.getXInstancesCount(); StringBuilder manaString = threadLocalBuilder.get(); @@ -728,8 +716,8 @@ public abstract class AbilityImpl implements Ability { } } addManaCostsToPay(new ManaCostsImpl<>(manaString.toString())); - getManaCostsToPay().setX(xValue * xValueMultiplier, amountMana); - setCostsTag("X", xValue * xValueMultiplier); + getManaCostsToPay().setX(xValue, amountMana); + setCostsTag("X", xValue); } variableManaCost.setPaid(); } diff --git a/Mage/src/main/java/mage/abilities/common/ActivateAbilityTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/ActivateAbilityTriggeredAbility.java new file mode 100644 index 00000000000..042a66819c5 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/ActivateAbilityTriggeredAbility.java @@ -0,0 +1,73 @@ +package mage.abilities.common; + +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.Effect; +import mage.constants.SetTargetPointer; +import mage.constants.Zone; +import mage.filter.FilterStackObject; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.stack.StackAbility; +import mage.target.targetpointer.FixedTarget; + +public class ActivateAbilityTriggeredAbility extends TriggeredAbilityImpl { + + private final FilterStackObject filter; + protected final SetTargetPointer setTargetPointer; + + public ActivateAbilityTriggeredAbility(Effect effect, FilterStackObject filter, SetTargetPointer setTargetPointer) { + this(Zone.BATTLEFIELD, effect, filter, setTargetPointer); + } + + public ActivateAbilityTriggeredAbility(Zone zone, Effect effect, FilterStackObject filter, SetTargetPointer setTargetPointer) { + super(zone, effect, false); + this.filter = filter; + this.setTargetPointer = setTargetPointer; + setTriggerPhrase("Whenever you activate " + filter.getMessage() + ", "); + } + + private ActivateAbilityTriggeredAbility(final ActivateAbilityTriggeredAbility ability) { + super(ability); + this.filter = ability.filter; + this.setTargetPointer = ability.setTargetPointer; + } + + @Override + public ActivateAbilityTriggeredAbility copy() { + return new ActivateAbilityTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ACTIVATED_ABILITY; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (!event.getPlayerId().equals(getControllerId())) { + return false; + } + StackAbility stackAbility = (StackAbility) game.getStack().getStackObject(event.getTargetId()); + if (stackAbility == null) { + return false; + } + + if (!filter.match(stackAbility, event.getPlayerId(), this, game)) { + return false; + } + + switch (setTargetPointer) { + case NONE: + break; + case PLAYER: + getAllEffects().setTargetPointer(new FixedTarget(getControllerId(), game)); + break; + case SPELL: + getAllEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game)); + break; + default: + throw new UnsupportedOperationException("Unexpected setTargetPointer in ActivateAbilityTriggeredAbility: " + setTargetPointer); + } + return true; + } +} diff --git a/Mage/src/main/java/mage/abilities/common/ActivatePlaneswalkerLoyaltyAbilityTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/ActivatePlaneswalkerLoyaltyAbilityTriggeredAbility.java index 24ff936b8be..d2bf1c85718 100644 --- a/Mage/src/main/java/mage/abilities/common/ActivatePlaneswalkerLoyaltyAbilityTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/ActivatePlaneswalkerLoyaltyAbilityTriggeredAbility.java @@ -3,26 +3,31 @@ package mage.abilities.common; import mage.abilities.LoyaltyAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; +import mage.constants.SetTargetPointer; import mage.constants.SubType; import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.stack.StackAbility; +import mage.target.targetpointer.FixedTarget; public class ActivatePlaneswalkerLoyaltyAbilityTriggeredAbility extends TriggeredAbilityImpl { private final SubType planeswalkerSubType; + protected final SetTargetPointer setTargetPointer; - public ActivatePlaneswalkerLoyaltyAbilityTriggeredAbility(Effect effect, SubType planeswalkerSubType) { + public ActivatePlaneswalkerLoyaltyAbilityTriggeredAbility(Effect effect, SubType planeswalkerSubType, SetTargetPointer setTargetPointer) { super(Zone.BATTLEFIELD, effect, false); this.planeswalkerSubType = planeswalkerSubType; + this.setTargetPointer = setTargetPointer; setTriggerPhrase("Whenever you activate a loyalty ability of a " + planeswalkerSubType.getDescription() + " planeswalker, "); } private ActivatePlaneswalkerLoyaltyAbilityTriggeredAbility(final ActivatePlaneswalkerLoyaltyAbilityTriggeredAbility ability) { super(ability); this.planeswalkerSubType = ability.planeswalkerSubType; + this.setTargetPointer = ability.setTargetPointer; } @Override @@ -49,7 +54,22 @@ public class ActivatePlaneswalkerLoyaltyAbilityTriggeredAbility extends Triggere || !permanent.hasSubtype(planeswalkerSubType, game)) { return false; } - this.getEffects().setValue("stackObject", stackAbility); + + switch (setTargetPointer) { + case NONE: + break; + case PLAYER: + getAllEffects().setTargetPointer(new FixedTarget(getControllerId(), game)); + break; + case SPELL: + getAllEffects().setTargetPointer(new FixedTarget(event.getTargetId(), game)); + break; + case PERMANENT: + getAllEffects().setTargetPointer(new FixedTarget(event.getSourceId(), game)); + break; + default: + throw new UnsupportedOperationException("Unexpected setTargetPointer in ActivatePlaneswalkerLoyaltyAbilityTriggeredAbility: " + setTargetPointer); + } return true; } } diff --git a/Mage/src/main/java/mage/abilities/costs/mana/ManaCostsImpl.java b/Mage/src/main/java/mage/abilities/costs/mana/ManaCostsImpl.java index d347227a078..f3c33c02a60 100644 --- a/Mage/src/main/java/mage/abilities/costs/mana/ManaCostsImpl.java +++ b/Mage/src/main/java/mage/abilities/costs/mana/ManaCostsImpl.java @@ -418,7 +418,6 @@ public class ManaCostsImpl extends ArrayList implements M game.undo(playerId); this.clearPaid(); - // TODO: checks Word of Command with Unbound Flourishing's X multiplier // TODO: checks Word of Command with {X}{X} cards int amount = 0; List variableCosts = getVariableCosts(); diff --git a/Mage/src/main/java/mage/abilities/effects/common/CopyStackObjectEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CopyStackObjectEffect.java index a8a14037cc3..a1a6096964f 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CopyStackObjectEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CopyStackObjectEffect.java @@ -3,18 +3,25 @@ package mage.abilities.effects.common; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.constants.Outcome; +import mage.constants.Zone; import mage.game.Game; import mage.game.stack.StackObject; import mage.players.Player; +import java.util.UUID; + /** * @author TheElk801 */ public class CopyStackObjectEffect extends OneShotEffect { public CopyStackObjectEffect() { + this("that ability"); + } + + public CopyStackObjectEffect(String name) { super(Outcome.Copy); - staticText = "copy that ability. You may choose new targets for the copy"; + staticText = "copy "+ name + ". You may choose new targets for the copy"; } private CopyStackObjectEffect(final CopyStackObjectEffect effect) { @@ -29,11 +36,15 @@ public class CopyStackObjectEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - StackObject ability = (StackObject) getValue("stackObject"); - if (controller == null || ability == null) { + UUID id = getTargetPointer().getFirst(game, source); + StackObject object = game.getStack().getStackObject(id); + if (object == null) { + object = (StackObject) game.getLastKnownInformation(id, Zone.STACK); + } + if (controller == null || object == null) { return false; } - ability.createCopyOnStack(game, source, source.getControllerId(), true); + object.createCopyOnStack(game, source, source.getControllerId(), true); return true; } } diff --git a/Mage/src/main/java/mage/abilities/meta/OrTriggeredAbility.java b/Mage/src/main/java/mage/abilities/meta/OrTriggeredAbility.java index c4128665c58..c5fce43af6c 100644 --- a/Mage/src/main/java/mage/abilities/meta/OrTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/meta/OrTriggeredAbility.java @@ -72,9 +72,13 @@ public class OrTriggeredAbility extends TriggeredAbilityImpl { public boolean checkTrigger(GameEvent event, Game game) { boolean toRet = false; for (TriggeredAbility ability : triggeredAbilities) { + for (Effect e : getEffects()) { //Add effects to the sub-abilities so that they can set target pointers + ability.addEffect(e); + } if (ability.checkEventType(event, game) && ability.checkTrigger(event, game)) { toRet = true; } + ability.getEffects().clear(); //Remove afterwards, ensures that they remain synced even with copying } return toRet; } diff --git a/Mage/src/main/java/mage/filter/predicate/other/AbilitySourceAttachedPredicate.java b/Mage/src/main/java/mage/filter/predicate/other/AbilitySourceAttachedPredicate.java new file mode 100644 index 00000000000..8d3bb1834d1 --- /dev/null +++ b/Mage/src/main/java/mage/filter/predicate/other/AbilitySourceAttachedPredicate.java @@ -0,0 +1,30 @@ +package mage.filter.predicate.other; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.game.Game; +import mage.game.stack.StackAbility; +import mage.game.stack.StackObject; + +/** + * @author notgreat + */ +public enum AbilitySourceAttachedPredicate implements ObjectSourcePlayerPredicate { + instance; + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + MageObject obj = input.getObject(); + Ability source = input.getSource(); + + return obj instanceof StackAbility + && ((StackAbility) obj).getSourceId().equals(source.getSourcePermanentOrLKI(game).getAttachedTo()); + } + + @Override + public String toString() { + return "Attached activated/triggered"; + } +} diff --git a/Mage/src/main/java/mage/filter/predicate/other/NotManaAbilityPredicate.java b/Mage/src/main/java/mage/filter/predicate/other/NotManaAbilityPredicate.java new file mode 100644 index 00000000000..0d7c8c3ea46 --- /dev/null +++ b/Mage/src/main/java/mage/filter/predicate/other/NotManaAbilityPredicate.java @@ -0,0 +1,26 @@ +package mage.filter.predicate.other; + +import mage.abilities.Ability; +import mage.filter.predicate.Predicate; +import mage.game.Game; +import mage.game.stack.StackObject; + +/** + * @author notgreat + */ +public enum NotManaAbilityPredicate implements Predicate { + instance; + + @Override + public boolean apply(StackObject input, Game game) { + if (!(input instanceof Ability)) { + return false; + } + return !((Ability) input).isManaAbility(); + } + + @Override + public String toString() { + return "isn't a mana ability"; + } +} diff --git a/Mage/src/main/java/mage/game/command/emblems/RowanKenrithEmblem.java b/Mage/src/main/java/mage/game/command/emblems/RowanKenrithEmblem.java index 7bd70e61b15..6a81caa03c7 100644 --- a/Mage/src/main/java/mage/game/command/emblems/RowanKenrithEmblem.java +++ b/Mage/src/main/java/mage/game/command/emblems/RowanKenrithEmblem.java @@ -1,22 +1,28 @@ package mage.game.command.emblems; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.ActivateAbilityTriggeredAbility; import mage.abilities.effects.common.CopyStackObjectEffect; +import mage.constants.SetTargetPointer; import mage.constants.Zone; -import mage.game.Game; +import mage.filter.FilterStackObject; +import mage.filter.common.FilterActivatedOrTriggeredAbility; +import mage.filter.predicate.other.NotManaAbilityPredicate; import mage.game.command.Emblem; -import mage.game.events.GameEvent; -import mage.game.stack.StackAbility; /** * @author TheElk801 */ public final class RowanKenrithEmblem extends Emblem { // Target player gets an emblem with "Whenever you activate an ability that isn't a mana ability, copy it. You may choose new targets for the copy." + private static final FilterStackObject filter = new FilterActivatedOrTriggeredAbility("an ability that isn't a mana ability"); + + static { + filter.add(NotManaAbilityPredicate.instance); + } public RowanKenrithEmblem() { super("Emblem Rowan Kenrith"); - this.getAbilities().add(new RowanKenrithEmblemTriggeredAbility()); + this.getAbilities().add(new ActivateAbilityTriggeredAbility(Zone.COMMAND, new CopyStackObjectEffect("it"), filter, SetTargetPointer.SPELL)); } private RowanKenrithEmblem(final RowanKenrithEmblem card) { @@ -28,42 +34,3 @@ public final class RowanKenrithEmblem extends Emblem { return new RowanKenrithEmblem(this); } } - -class RowanKenrithEmblemTriggeredAbility extends TriggeredAbilityImpl { - - RowanKenrithEmblemTriggeredAbility() { - super(Zone.COMMAND, new CopyStackObjectEffect(), false); - } - - private RowanKenrithEmblemTriggeredAbility(final RowanKenrithEmblemTriggeredAbility ability) { - super(ability); - } - - @Override - public RowanKenrithEmblemTriggeredAbility copy() { - return new RowanKenrithEmblemTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ACTIVATED_ABILITY; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (!event.getPlayerId().equals(getControllerId())) { - return false; - } - StackAbility stackAbility = (StackAbility) game.getStack().getStackObject(event.getSourceId()); - if (stackAbility == null || stackAbility.getStackAbility().isManaActivatedAbility()) { - return false; - } - this.getEffects().setValue("stackObject", stackAbility); - return true; - } - - @Override - public String getRule() { - return "Whenever you activate an ability that isn't a mana ability, copy it. You may choose new targets for the copy."; - } -} diff --git a/Mage/src/main/java/mage/game/events/GameEvent.java b/Mage/src/main/java/mage/game/events/GameEvent.java index ee4a1a97345..0ce9376908b 100644 --- a/Mage/src/main/java/mage/game/events/GameEvent.java +++ b/Mage/src/main/java/mage/game/events/GameEvent.java @@ -219,13 +219,6 @@ public class GameEvent implements Serializable { sourceId sourceId of the mount playerId the id of the controlling player */ - X_MANA_ANNOUNCE, - /* X_MANA_ANNOUNCE - mana x-costs announced by players (X value can be changed by replace events like Unbound Flourishing) - targetId id of the spell that's cast - playerId player that casts the spell or ability - amount X multiplier to change X value, default 1 - */ CAST_SPELL, CAST_SPELL_LATE, /* SPELL_CAST, CAST_SPELL_LATE diff --git a/Mage/src/main/java/mage/game/stack/SpellStack.java b/Mage/src/main/java/mage/game/stack/SpellStack.java index 389495d22e4..1cbd03da71e 100644 --- a/Mage/src/main/java/mage/game/stack/SpellStack.java +++ b/Mage/src/main/java/mage/game/stack/SpellStack.java @@ -1,18 +1,19 @@ package mage.game.stack; -import java.util.ArrayDeque; -import java.util.Date; -import java.util.UUID; - import mage.MageObject; import mage.abilities.Ability; import mage.constants.PutCards; +import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; import mage.util.CardUtil; import org.apache.log4j.Logger; +import java.util.ArrayDeque; +import java.util.Date; +import java.util.UUID; + /** * @author BetaSteward_at_googlemail.com */ @@ -82,6 +83,7 @@ public class SpellStack extends ArrayDeque { if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.COUNTER, objectId, source, stackObject.getControllerId()))) { if (!(stackObject instanceof Spell)) { // spells are removed from stack by the card movement this.remove(stackObject, game); + game.rememberLKI(Zone.STACK, stackObject); } stackObject.counter(source, game, putCard); if (!game.isSimulation()) { diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java index d0bd663c025..1e61d11ce50 100644 --- a/Mage/src/main/java/mage/players/Player.java +++ b/Mage/src/main/java/mage/players/Player.java @@ -731,11 +731,7 @@ public interface Player extends MageItem, Copyable { boolean shuffleCardsToLibrary(Card card, Game game, Ability source); // set the value for X mana spells and abilities - default int announceXMana(int min, int max, String message, Game game, Ability ability) { - return announceXMana(min, max, 1, message, game, ability); - } - - int announceXMana(int min, int max, int multiplier, String message, Game game, Ability ability); + int announceXMana(int min, int max, String message, Game game, Ability ability); // set the value for non mana X costs int announceXCost(int min, int max, String message, Game game, Ability ability, VariableCost variableCost); diff --git a/Mage/src/main/java/mage/players/StubPlayer.java b/Mage/src/main/java/mage/players/StubPlayer.java index a79c48f3ec8..7ddc102e262 100644 --- a/Mage/src/main/java/mage/players/StubPlayer.java +++ b/Mage/src/main/java/mage/players/StubPlayer.java @@ -156,7 +156,7 @@ public class StubPlayer extends PlayerImpl { } @Override - public int announceXMana(int min, int max, int multiplier, String message, Game game, Ability ability) { + public int announceXMana(int min, int max, String message, Game game, Ability ability) { return 0; }