From 5db4beac6c32ce86585af677ce99c19c5f1b523e Mon Sep 17 00:00:00 2001 From: ssk97 Date: Fri, 20 Jun 2025 18:58:13 -0700 Subject: [PATCH] Adding targets (Part 3/3) (#13769) Adds target and/or target adjuster to cards whose abilities have the word "target", cards S-Z. Add `spellCast` value to `CastSpellPaidBySourceTriggeredAbility`. --- .../src/mage/cards/s/ScalelordReckoner.java | 50 ++----- Mage.Sets/src/mage/cards/s/SkymarkRoc.java | 62 ++------ .../src/mage/cards/s/SoltariVisionary.java | 61 ++------ Mage.Sets/src/mage/cards/t/TheAbyss.java | 73 ++++----- Mage.Sets/src/mage/cards/t/TheLordOfPain.java | 119 +++++++-------- .../src/mage/cards/t/TheMyriadPools.java | 141 ++++-------------- Mage.Sets/src/mage/cards/t/ThranTome.java | 29 +--- .../src/mage/cards/t/ToralfGodOfFury.java | 2 + .../src/mage/cards/v/VenerableWarsinger.java | 64 ++------ .../src/mage/cards/w/WebstrikeElite.java | 61 ++------ .../single/lci/BarracksOfTheThousandTest.java | 16 +- .../cards/single/lci/TheMyriadPoolsTest.java | 27 ++++ ...CastSpellPaidBySourceTriggeredAbility.java | 3 +- 13 files changed, 209 insertions(+), 499 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/lci/TheMyriadPoolsTest.java diff --git a/Mage.Sets/src/mage/cards/s/ScalelordReckoner.java b/Mage.Sets/src/mage/cards/s/ScalelordReckoner.java index 3ff98c16e20..5f588714be6 100644 --- a/Mage.Sets/src/mage/cards/s/ScalelordReckoner.java +++ b/Mage.Sets/src/mage/cards/s/ScalelordReckoner.java @@ -1,6 +1,7 @@ package mage.cards.s; import mage.MageInt; +import mage.abilities.Ability; import mage.abilities.common.BecomesTargetAnyTriggeredAbility; import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.keyword.FlyingAbility; @@ -9,21 +10,21 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SetTargetPointer; import mage.constants.SubType; +import mage.filter.FilterPermanent; import mage.filter.StaticFilters; import mage.filter.common.FilterControlledPermanent; import mage.filter.common.FilterNonlandPermanent; -import mage.filter.predicate.permanent.ControllerIdPredicate; -import mage.game.Game; -import mage.game.events.GameEvent; import mage.target.TargetPermanent; +import mage.target.targetadjustment.ThatPlayerControlsTargetAdjuster; import java.util.UUID; /** - * * @author spjspj */ public final class ScalelordReckoner extends CardImpl { + static FilterPermanent filterDragon = new FilterControlledPermanent(SubType.DRAGON, "a Dragon you control"); + static FilterPermanent filterTarget = new FilterNonlandPermanent("nonland permanent that player controls"); public ScalelordReckoner(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}{W}"); @@ -36,7 +37,10 @@ public final class ScalelordReckoner extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // Whenever a Dragon you control becomes the target of a spell or ability an opponent controls, destroy target nonland permanent that player controls. - this.addAbility(new ScalelordReckonerTriggeredAbility()); + Ability ability = new BecomesTargetAnyTriggeredAbility(new DestroyTargetEffect(), filterDragon, StaticFilters.FILTER_SPELL_OR_ABILITY_OPPONENTS, SetTargetPointer.PLAYER, false); + ability.addTarget(new TargetPermanent(filterTarget)); + ability.setTargetAdjuster(new ThatPlayerControlsTargetAdjuster()); + this.addAbility(ability); } private ScalelordReckoner(final ScalelordReckoner card) { @@ -48,39 +52,3 @@ public final class ScalelordReckoner extends CardImpl { return new ScalelordReckoner(this); } } - -class ScalelordReckonerTriggeredAbility extends BecomesTargetAnyTriggeredAbility { - - private static final FilterControlledPermanent filter = new FilterControlledPermanent("a Dragon you control"); - - static { - filter.add(SubType.DRAGON.getPredicate()); - } - - ScalelordReckonerTriggeredAbility() { - super(new DestroyTargetEffect().setText("destroy target nonland permanent that player controls"), - filter, StaticFilters.FILTER_SPELL_OR_ABILITY_OPPONENTS, SetTargetPointer.NONE, false); - } - - private ScalelordReckonerTriggeredAbility(final ScalelordReckonerTriggeredAbility ability) { - super(ability); - } - - @Override - public ScalelordReckonerTriggeredAbility copy() { - return new ScalelordReckonerTriggeredAbility(this); - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (!super.checkTrigger(event, game)) { - return false; - } - FilterNonlandPermanent targetFilter = new FilterNonlandPermanent("nonland permanent that player controls"); - targetFilter.add(new ControllerIdPredicate(event.getPlayerId())); - this.getTargets().clear(); - this.addTarget(new TargetPermanent(targetFilter)); - return true; - } - -} diff --git a/Mage.Sets/src/mage/cards/s/SkymarkRoc.java b/Mage.Sets/src/mage/cards/s/SkymarkRoc.java index 30456421ff6..22fa0ad2d9c 100644 --- a/Mage.Sets/src/mage/cards/s/SkymarkRoc.java +++ b/Mage.Sets/src/mage/cards/s/SkymarkRoc.java @@ -3,21 +3,21 @@ package mage.cards.s; import mage.MageInt; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; import mage.abilities.effects.common.ReturnToHandTargetEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.ComparisonType; +import mage.constants.SetTargetPointer; import mage.constants.SubType; -import mage.constants.Zone; +import mage.filter.FilterPermanent; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.mageobject.ToughnessPredicate; -import mage.filter.predicate.permanent.ControllerIdPredicate; -import mage.game.Game; -import mage.game.events.GameEvent; import mage.target.TargetPermanent; +import mage.target.targetadjustment.ThatPlayerControlsTargetAdjuster; import java.util.UUID; @@ -26,6 +26,12 @@ import java.util.UUID; */ public final class SkymarkRoc extends CardImpl { + static FilterPermanent filter = new FilterCreaturePermanent("creature defending player controls with toughness 2 or less"); + + static { + filter.add(new ToughnessPredicate(ComparisonType.OR_LESS, 2)); + } + public SkymarkRoc(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}{U}"); this.subtype.add(SubType.BIRD); @@ -37,7 +43,10 @@ public final class SkymarkRoc extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // Whenever Skymark Roc attacks, you may return target creature defending player controls with toughness 2 or less to its owner's hand. - this.addAbility(new SkymarkRocAbility()); + Ability ability = new AttacksTriggeredAbility(new ReturnToHandTargetEffect(), true, null, SetTargetPointer.PLAYER); + ability.addTarget(new TargetPermanent(filter)); + ability.setTargetAdjuster(new ThatPlayerControlsTargetAdjuster()); + this.addAbility(ability); } private SkymarkRoc(final SkymarkRoc card) { @@ -49,44 +58,3 @@ public final class SkymarkRoc extends CardImpl { return new SkymarkRoc(this); } } - -class SkymarkRocAbility extends TriggeredAbilityImpl { - - public SkymarkRocAbility() { - super(Zone.BATTLEFIELD, new ReturnToHandTargetEffect(), true); - } - - private SkymarkRocAbility(final SkymarkRocAbility ability) { - super(ability); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ATTACKER_DECLARED; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (event.getSourceId().equals(this.getSourceId())) { - FilterCreaturePermanent filter = new FilterCreaturePermanent("creature defending player controls with toughness 2 or less"); - UUID defenderId = game.getCombat().getDefendingPlayerId(sourceId, game); - filter.add(new ControllerIdPredicate(defenderId)); - filter.add(new ToughnessPredicate(ComparisonType.FEWER_THAN, 3)); - - this.getTargets().clear(); - this.addTarget(new TargetPermanent(filter)); - return true; - } - return false; - } - - @Override - public String getRule() { - return "Whenever {this} attacks, you may return target creature defending player controls with toughness 2 or less to its owner's hand."; - } - - @Override - public SkymarkRocAbility copy() { - return new SkymarkRocAbility(this); - } -} diff --git a/Mage.Sets/src/mage/cards/s/SoltariVisionary.java b/Mage.Sets/src/mage/cards/s/SoltariVisionary.java index aef0cc4f2a6..2bb908b3657 100644 --- a/Mage.Sets/src/mage/cards/s/SoltariVisionary.java +++ b/Mage.Sets/src/mage/cards/s/SoltariVisionary.java @@ -1,30 +1,28 @@ package mage.cards.s; -import java.util.UUID; import mage.MageInt; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.Ability; +import mage.abilities.common.DealsDamageToAPlayerTriggeredAbility; import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.keyword.ShadowAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; +import mage.filter.FilterPermanent; import mage.filter.common.FilterEnchantmentPermanent; -import mage.filter.predicate.permanent.ControllerIdPredicate; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; -import mage.game.permanent.Permanent; import mage.target.TargetPermanent; +import mage.target.targetadjustment.ThatPlayerControlsTargetAdjuster; + +import java.util.UUID; /** * * @author jeffwadsworth */ public final class SoltariVisionary extends CardImpl { - + static FilterPermanent filter = new FilterEnchantmentPermanent("enchantment that player controls"); public SoltariVisionary(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{W}{W}"); this.subtype.add(SubType.SOLTARI); @@ -37,7 +35,10 @@ public final class SoltariVisionary extends CardImpl { this.addAbility(ShadowAbility.getInstance()); // Whenever Soltari Visionary deals damage to a player, destroy target enchantment that player controls. - this.addAbility(new SoltariVisionaryTriggeredAbility()); + Ability ability = new DealsDamageToAPlayerTriggeredAbility(new DestroyTargetEffect(), false, true); + ability.addTarget(new TargetPermanent(filter)); + ability.setTargetAdjuster(new ThatPlayerControlsTargetAdjuster()); + this.addAbility(ability); } private SoltariVisionary(final SoltariVisionary card) { @@ -49,43 +50,3 @@ public final class SoltariVisionary extends CardImpl { return new SoltariVisionary(this); } } - -class SoltariVisionaryTriggeredAbility extends TriggeredAbilityImpl { - - SoltariVisionaryTriggeredAbility() { - super(Zone.BATTLEFIELD, new DestroyTargetEffect(), false); - } - - private SoltariVisionaryTriggeredAbility(final SoltariVisionaryTriggeredAbility ability) { - super(ability); - } - - @Override - public SoltariVisionaryTriggeredAbility copy() { - return new SoltariVisionaryTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PLAYER; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - Permanent soltari = game.getPermanent(event.getSourceId()); - if (soltari != null && soltari.getId().equals(this.getSourceId())) { - FilterEnchantmentPermanent filter = new FilterEnchantmentPermanent("enchantment that player controls."); - filter.add(new ControllerIdPredicate(event.getPlayerId())); - filter.setMessage("enchantment controlled by " + game.getPlayer(event.getTargetId()).getLogName()); - this.getTargets().clear(); - this.addTarget(new TargetPermanent(filter)); - return true; - } - return false; - } - - @Override - public String getRule() { - return "Whenever {this} deals damage to a player, destroy target enchantment that player controls."; - } -} diff --git a/Mage.Sets/src/mage/cards/t/TheAbyss.java b/Mage.Sets/src/mage/cards/t/TheAbyss.java index ce9607c3d2c..6ae2fc351e8 100644 --- a/Mage.Sets/src/mage/cards/t/TheAbyss.java +++ b/Mage.Sets/src/mage/cards/t/TheAbyss.java @@ -1,36 +1,43 @@ package mage.cards.t; -import java.util.UUID; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.Ability; import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SuperType; -import mage.constants.Zone; +import mage.constants.TargetController; +import mage.filter.FilterPermanent; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.permanent.ControllerIdPredicate; import mage.game.Game; -import mage.game.events.GameEvent; -import mage.players.Player; import mage.target.Target; import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; +import mage.target.targetadjustment.TargetAdjuster; +import mage.target.targetpointer.FirstTargetPointer; + +import java.util.UUID; /** * * @author emerald000 */ public final class TheAbyss extends CardImpl { + static FilterPermanent filter = new FilterPermanent("nonartifact creature that player controls of their choice"); public TheAbyss(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{3}{B}"); this.supertype.add(SuperType.WORLD); // At the beginning of each player's upkeep, destroy target nonartifact creature that player controls of their choice. It can't be regenerated. - this.addAbility(new TheAbyssTriggeredAbility()); + Ability ability = new BeginningOfUpkeepTriggeredAbility(TargetController.EACH_PLAYER, + new DestroyTargetEffect(true), false).withTargetPointerSet(true); + ability.addTarget(new TargetPermanent(filter)); // Only used for text generation + ability.setTargetAdjuster(TheAbyssTargetAdjuster.instance); + this.addAbility(ability); } private TheAbyss(final TheAbyss card) { @@ -43,45 +50,23 @@ public final class TheAbyss extends CardImpl { } } -class TheAbyssTriggeredAbility extends TriggeredAbilityImpl { - - TheAbyssTriggeredAbility() { - super(Zone.BATTLEFIELD, new DestroyTargetEffect(true), false); +enum TheAbyssTargetAdjuster implements TargetAdjuster { + instance; + private static final FilterPermanent filter + = new FilterCreaturePermanent("nonartifact creature that player controls of their choice"); + static { + filter.add(Predicates.not(CardType.ARTIFACT.getPredicate())); } - - private TheAbyssTriggeredAbility(final TheAbyssTriggeredAbility ability) { - super(ability); - } - @Override - public TheAbyssTriggeredAbility copy() { - return new TheAbyssTriggeredAbility(this); - } + public void adjustTargets(Ability ability, Game game) { + UUID playerId = ability.getEffects().get(0).getTargetPointer().getFirst(game, ability); + ability.getTargets().clear(); + ability.getAllEffects().setTargetPointer(new FirstTargetPointer()); - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.UPKEEP_STEP_PRE; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - Player player = game.getPlayer(event.getPlayerId()); - if (player != null) { - FilterCreaturePermanent filter = new FilterCreaturePermanent("nonartifact creature you control"); - filter.add(Predicates.not(CardType.ARTIFACT.getPredicate())); - filter.add(new ControllerIdPredicate(player.getId())); - Target target = new TargetPermanent(filter); - target.setAbilityController(getControllerId()); - target.setTargetController(player.getId()); - this.getTargets().clear(); - this.getTargets().add(target); - return true; - } - return false; - } - - @Override - public String getRule() { - return "At the beginning of each player's upkeep, destroy target nonartifact creature that player controls of their choice. It can't be regenerated."; + FilterPermanent adjustedFilter = filter.copy(); + adjustedFilter.add(new ControllerIdPredicate(playerId)); + Target newTarget = new TargetPermanent(adjustedFilter); + newTarget.setTargetController(playerId); + ability.addTarget(newTarget); } } diff --git a/Mage.Sets/src/mage/cards/t/TheLordOfPain.java b/Mage.Sets/src/mage/cards/t/TheLordOfPain.java index 470a4556562..e6bd1b89631 100644 --- a/Mage.Sets/src/mage/cards/t/TheLordOfPain.java +++ b/Mage.Sets/src/mage/cards/t/TheLordOfPain.java @@ -1,38 +1,46 @@ package mage.cards.t; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SpellCastAllTriggeredAbility; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.continuous.CantGainLifeAllEffect; -import mage.constants.*; import mage.abilities.keyword.MenaceAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.*; import mage.filter.FilterPlayer; import mage.filter.FilterSpell; import mage.filter.predicate.Predicate; import mage.filter.predicate.Predicates; -import mage.filter.predicate.mageobject.MageObjectReferencePredicate; +import mage.filter.predicate.other.PlayerIdPredicate; import mage.game.Game; -import mage.game.events.GameEvent; import mage.game.stack.Spell; import mage.game.stack.StackObject; -import mage.players.Player; +import mage.target.Target; import mage.target.TargetPlayer; -import mage.target.targetpointer.FixedTarget; +import mage.target.targetadjustment.TargetAdjuster; +import mage.target.targetpointer.FirstTargetPointer; import mage.watchers.common.SpellsCastWatcher; +import java.util.UUID; + /** - * * @author Grath */ public final class TheLordOfPain extends CardImpl { + private static final FilterSpell filter = new FilterSpell("their first spell each turn"); + + static { + filter.add(TheLordOfPainPredicate.instance); + } + public TheLordOfPain(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}{R}"); - + this.supertype.add(SuperType.LEGENDARY); this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.ASSASSIN); @@ -48,7 +56,12 @@ public final class TheLordOfPain extends CardImpl { )); // Whenever a player casts their first spell each turn, choose another target player. The Lord of Pain deals damage equal to that spell's mana value to the chosen player. - this.addAbility(new TheLordOfPainTriggeredAbility()); + Ability ability = new SpellCastAllTriggeredAbility( + new DamageTargetEffect(TheLordOfPainValue.instance) + .setText("choose another target player. {this} deals damage equal to that spell's mana value to the chosen player") + , filter, false, SetTargetPointer.PLAYER); + ability.setTargetAdjuster(TheLordOfPainTargetAdjuster.instance); + this.addAbility(ability); } private TheLordOfPain(final TheLordOfPain card) { @@ -72,70 +85,40 @@ enum TheLordOfPainPredicate implements Predicate { } } -class TheLordOfPainTriggeredAbility extends SpellCastAllTriggeredAbility { - private static final FilterSpell filter = new FilterSpell("their first spell each turn"); - - static { - filter.add(TheLordOfPainPredicate.instance); - } - - public TheLordOfPainTriggeredAbility() { - super(new TheLordOfPainEffect(), filter, false, SetTargetPointer.PLAYER); - } - - protected TheLordOfPainTriggeredAbility(final TheLordOfPainTriggeredAbility ability) { - super(ability); - } +enum TheLordOfPainTargetAdjuster implements TargetAdjuster { + instance; @Override - public TheLordOfPainTriggeredAbility copy() { - return new TheLordOfPainTriggeredAbility(this); - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (super.checkTrigger(event, game)) { - Player controller = game.getPlayer(getControllerId()); - Spell spell = (Spell)getEffects().get(0).getValue("spellCast"); - if (controller != null) { - FilterPlayer filter2 = new FilterPlayer("another target player"); - filter2.add(Predicates.not(new MageObjectReferencePredicate(spell.getControllerId(), game))); - TargetPlayer target = new TargetPlayer(1, 1, false, filter2); - controller.choose(Outcome.Damage, target, this, game); - getEffects().setTargetPointer(new FixedTarget(target.getFirstTarget())); - return true; - } - } - return false; + public void adjustTargets(Ability ability, Game game) { + UUID opponentId = ability.getEffects().get(0).getTargetPointer().getFirst(game, ability); + ability.getTargets().clear(); + ability.getAllEffects().setTargetPointer(new FirstTargetPointer()); + FilterPlayer filter = new FilterPlayer("another target player"); + filter.add(Predicates.not(new PlayerIdPredicate(opponentId))); + Target newTarget = new TargetPlayer(filter); + ability.addTarget(newTarget); } } -class TheLordOfPainEffect extends OneShotEffect { - - TheLordOfPainEffect() { - super(Outcome.Benefit); - staticText = "choose another target player. {this} deals damage equal to that spell's mana value to the chosen player"; - } - - private TheLordOfPainEffect(final TheLordOfPainEffect effect) { - super(effect); - } +enum TheLordOfPainValue implements DynamicValue { + instance; @Override - public TheLordOfPainEffect copy() { - return new TheLordOfPainEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Spell spell = (Spell)this.getValue("spellCast"); - if (spell != null) { - int cost = spell.getManaValue(); - Player target = game.getPlayer(getTargetPointer().getFirst(game, source)); - if (target != null) { - target.damage(cost, source.getSourceId(), source, game); - return true; - } + public int calculate(Game game, Ability sourceAbility, Effect effect) { + Object spell = effect.getValue("spellCast"); + if (spell instanceof Spell) { + return ((Spell) spell).getManaValue(); } - return false; } + return 0; + } + + @Override + public TheLordOfPainValue copy() { + return instance; + } + + @Override + public String getMessage() { + return "that spell's mana value"; + } } diff --git a/Mage.Sets/src/mage/cards/t/TheMyriadPools.java b/Mage.Sets/src/mage/cards/t/TheMyriadPools.java index 7d9e8b3e7f8..bcde9f4288f 100644 --- a/Mage.Sets/src/mage/cards/t/TheMyriadPools.java +++ b/Mage.Sets/src/mage/cards/t/TheMyriadPools.java @@ -1,38 +1,39 @@ package mage.cards.t; -import java.util.UUID; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.effects.Effect; +import mage.abilities.common.CastSpellPaidBySourceTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CopyEffect; import mage.abilities.mana.BlueManaAbility; -import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.SuperType; -import mage.constants.WatcherScope; -import mage.constants.Zone; +import mage.filter.FilterSpell; import mage.filter.StaticFilters; +import mage.filter.predicate.mageobject.PermanentPredicate; import mage.game.Game; -import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.permanent.PermanentCard; import mage.game.stack.Spell; import mage.players.Player; import mage.target.TargetPermanent; -import mage.target.targetpointer.FixedTarget; -import mage.watchers.Watcher; + +import java.util.UUID; /** - * * @author jeffwadsworth */ public class TheMyriadPools extends CardImpl { + private static final FilterSpell filter = new FilterSpell("a permanent spell"); + + static { + filter.add(PermanentPredicate.instance); + } + public TheMyriadPools(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.LAND}, null); this.supertype.add(SuperType.LEGENDARY); @@ -41,11 +42,12 @@ public class TheMyriadPools extends CardImpl { this.nightCard = true; // {T}: Add {U}. - Ability ability = new BlueManaAbility(); - // Whenever you cast a permanent spell using mana produced by The Myriad Pools, up to one other target permanent you control becomes a copy of that spell until end of turn. - this.addAbility(ability, new TheMyriadPoolsWatcher(ability.getOriginalId().toString())); - this.addAbility(new TheMyriadPoolsTriggeredAbility()); + this.addAbility(new BlueManaAbility()); + // Whenever you cast a permanent spell using mana produced by The Myriad Pools, up to one other target permanent you control becomes a copy of that spell until end of turn. + Ability ability = new CastSpellPaidBySourceTriggeredAbility(new TheMyriadPoolsCopyEffect(), filter, false); + ability.addTarget(new TargetPermanent(0, 1, StaticFilters.FILTER_CONTROLLED_ANOTHER_TARGET_PERMANENT)); + this.addAbility(ability); } private TheMyriadPools(final TheMyriadPools card) { @@ -58,90 +60,11 @@ public class TheMyriadPools extends CardImpl { } } -class TheMyriadPoolsWatcher extends Watcher { - - private UUID permanentId = UUID.randomUUID(); - private final String originalId; - - public TheMyriadPoolsWatcher(String originalId) { - super(WatcherScope.CARD); - this.originalId = originalId; - } - - public boolean manaUsedToCastPermanentPart(UUID id) { - return permanentId.equals(id); - } - - @Override - public void watch(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.MANA_PAID) { - if (event.getData() != null - && event.getData().equals(originalId)) { - Spell spell = game.getStack().getSpell(event.getTargetId()); - if (spell != null - && spell.isPermanent(game)) { - Card card = spell.getCard(); - permanentId = card.getId(); - } - } - } - } - - @Override - public void reset() { - super.reset(); - } -} - -class TheMyriadPoolsTriggeredAbility extends TriggeredAbilityImpl { - - public TheMyriadPoolsTriggeredAbility() { - super(Zone.BATTLEFIELD, new TheMyriadPoolsCopyEffect()); - } - - private TheMyriadPoolsTriggeredAbility(final TheMyriadPoolsTriggeredAbility ability) { - super(ability); - } - - @Override - public TheMyriadPoolsTriggeredAbility copy() { - return new TheMyriadPoolsTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.SPELL_CAST; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - TheMyriadPoolsWatcher watcher = game.getState().getWatcher(TheMyriadPoolsWatcher.class, this.getSourceId()); - if (watcher != null - && watcher.manaUsedToCastPermanentPart(event.getSourceId())) { - Spell spell = game.getSpell(event.getSourceId()); - if (spell != null - && spell.isControlledBy(getControllerId())) { - for (Effect effect : this.getEffects()) { - effect.setTargetPointer(new FixedTarget(event.getSourceId())); - } - return true; - } - } - return false; - } - - @Override - public String getRule() { - return "Whenever you cast a permanent spell using mana produced by {this}, up to one other target permanent you control becomes a copy of that spell until end of turn."; - } - -} - class TheMyriadPoolsCopyEffect extends OneShotEffect { TheMyriadPoolsCopyEffect() { super(Outcome.Neutral); - this.staticText = "copy of card on stack"; + this.staticText = "up to one other target permanent you control becomes a copy of that spell until end of turn"; } private TheMyriadPoolsCopyEffect(final TheMyriadPoolsCopyEffect effect) { @@ -155,28 +78,18 @@ class TheMyriadPoolsCopyEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent targetPermanentToCopyTo = null; + Permanent targetPermanentToCopyTo = game.getPermanent(getTargetPointer().getFirst(game, source)); Player controller = game.getPlayer(source.getControllerId()); - if (controller == null) { + Object spell = getValue("spellCast"); + if (controller == null || targetPermanentToCopyTo == null || !(spell instanceof Spell)) { return false; } - TargetPermanent target = new TargetPermanent(0, 1, StaticFilters.FILTER_CONTROLLED_ANOTHER_PERMANENT, false); - if (controller.choose(Outcome.Neutral, target, source, game)) { - targetPermanentToCopyTo = game.getPermanent(target.getFirstTarget()); - } - Card copyFromCardOnStack = game.getCard(getTargetPointer().getFirst(game, source)); - Permanent newBluePrint = null; - if (targetPermanentToCopyTo != null) { - if (copyFromCardOnStack != null) { - newBluePrint = new PermanentCard(copyFromCardOnStack, source.getControllerId(), game); - newBluePrint.assignNewId(); - CopyEffect copyEffect = new CopyEffect(Duration.EndOfTurn, newBluePrint, targetPermanentToCopyTo.getId()); - Ability newAbility = source.copy(); - copyEffect.init(newAbility, game); - game.addEffect(copyEffect, newAbility); - } - return true; - } - return false; + Permanent newBluePrint = new PermanentCard(((Spell)spell).getCard(), source.getControllerId(), game); + newBluePrint.assignNewId(); + CopyEffect copyEffect = new CopyEffect(Duration.EndOfTurn, newBluePrint, targetPermanentToCopyTo.getId()); + Ability newAbility = source.copy(); + copyEffect.init(newAbility, game); + game.addEffect(copyEffect, newAbility); + return true; } } diff --git a/Mage.Sets/src/mage/cards/t/ThranTome.java b/Mage.Sets/src/mage/cards/t/ThranTome.java index ddf7b2cd564..974f50258b7 100644 --- a/Mage.Sets/src/mage/cards/t/ThranTome.java +++ b/Mage.Sets/src/mage/cards/t/ThranTome.java @@ -1,6 +1,5 @@ package mage.cards.t; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; @@ -13,11 +12,9 @@ import mage.constants.Zone; import mage.filter.FilterCard; import mage.game.Game; import mage.players.Player; -import mage.target.Target; import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetOpponent; -import java.util.Set; import java.util.UUID; /** @@ -31,6 +28,7 @@ public final class ThranTome extends CardImpl { // Reveal the top three cards of your library. Target opponent chooses one of those cards. Put that card into your graveyard, then draw two cards. Ability ability = new SimpleActivatedAbility(new ThranTomeEffect(), new ManaCostsImpl<>("{5}")); ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetOpponent()); this.addAbility(ability); } @@ -62,35 +60,18 @@ class ThranTomeEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - // validate source and controller exist + // validate opponent and controller exist Player controller = game.getPlayer(source.getControllerId()); - MageObject sourceObject = game.getObject(source); + Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); - if (sourceObject == null || controller == null) { + if (opponent == null || controller == null) { return false; } - // target an opponent, if able - Player opponent; - Set opponents = game.getOpponents(controller.getId()); - opponents.removeIf(opp -> !game.getPlayer(opp).canBeTargetedBy(sourceObject, source.getControllerId(), source, game)); - - if (opponents.isEmpty()) { - return false; - } else { - if (opponents.size() == 1) { - opponent = game.getPlayer(opponents.iterator().next()); - } else { - Target target = new TargetOpponent(); - controller.chooseTarget(Outcome.Detriment, target, source, game); - opponent = game.getPlayer(target.getFirstTarget()); - } - } - // reveal the cards and choose one. put it in the graveyard Card cardToGraveyard; Cards cards = new CardsImpl(controller.getLibrary().getTopCards(game, 3)); - controller.revealCards(sourceObject.getIdName(), cards, game); + controller.revealCards(source, cards, game); if (cards.size() == 1) { cardToGraveyard = cards.getRandom(game); diff --git a/Mage.Sets/src/mage/cards/t/ToralfGodOfFury.java b/Mage.Sets/src/mage/cards/t/ToralfGodOfFury.java index 74aee9a3904..3c697cd96e6 100644 --- a/Mage.Sets/src/mage/cards/t/ToralfGodOfFury.java +++ b/Mage.Sets/src/mage/cards/t/ToralfGodOfFury.java @@ -35,6 +35,7 @@ import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetAnyTarget; import mage.target.common.TargetPermanentOrPlayer; +import mage.target.targetadjustment.DefineByTriggerTargetAdjuster; import java.util.UUID; @@ -98,6 +99,7 @@ class ToralfGodOfFuryTriggeredAbility extends TriggeredAbilityImpl implements Ba ToralfGodOfFuryTriggeredAbility() { super(Zone.BATTLEFIELD, null); + this.setTargetAdjuster(DefineByTriggerTargetAdjuster.instance); } private ToralfGodOfFuryTriggeredAbility(final ToralfGodOfFuryTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/cards/v/VenerableWarsinger.java b/Mage.Sets/src/mage/cards/v/VenerableWarsinger.java index 269eb2ba72a..6566864ddba 100644 --- a/Mage.Sets/src/mage/cards/v/VenerableWarsinger.java +++ b/Mage.Sets/src/mage/cards/v/VenerableWarsinger.java @@ -1,7 +1,10 @@ package mage.cards.v; import mage.MageInt; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.Ability; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.dynamicvalue.common.EffectKeyValue; +import mage.abilities.effects.common.InfoEffect; import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; import mage.abilities.keyword.TrampleAbility; import mage.abilities.keyword.VigilanceAbility; @@ -10,15 +13,10 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.ComparisonType; import mage.constants.SubType; -import mage.constants.Zone; import mage.filter.FilterCard; import mage.filter.common.FilterCreatureCard; -import mage.filter.predicate.mageobject.ManaValuePredicate; -import mage.game.Game; -import mage.game.events.DamagedEvent; -import mage.game.events.GameEvent; -import mage.players.Player; import mage.target.common.TargetCardInYourGraveyard; +import mage.target.targetadjustment.ManaValueTargetAdjuster; import java.util.UUID; @@ -26,6 +24,7 @@ import java.util.UUID; * @author TheElk801 */ public final class VenerableWarsinger extends CardImpl { + FilterCard filter = new FilterCreatureCard("creature card with mana value X or less from your graveyard"); public VenerableWarsinger(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}{W}"); @@ -42,7 +41,11 @@ public final class VenerableWarsinger extends CardImpl { this.addAbility(TrampleAbility.getInstance()); // Whenever Venerable Warsinger deals combat damage to a player, you may return target creature card with mana value X or less from your graveyard to the battlefield, where X is the amount of damage that Venerable Warsinger dealt to that player. - this.addAbility(new VenerableWarsingerTriggeredAbility()); + Ability ability = new DealsCombatDamageToAPlayerTriggeredAbility(new ReturnFromGraveyardToBattlefieldTargetEffect(), true); + ability.addTarget(new TargetCardInYourGraveyard(filter)); + ability.setTargetAdjuster(new ManaValueTargetAdjuster(new EffectKeyValue("damage"), ComparisonType.OR_LESS)); + ability.addEffect(new InfoEffect("where X is the amount of damage {this} dealt to that player").concatBy(",")); + this.addAbility(ability); } private VenerableWarsinger(final VenerableWarsinger card) { @@ -54,48 +57,3 @@ public final class VenerableWarsinger extends CardImpl { return new VenerableWarsinger(this); } } - -class VenerableWarsingerTriggeredAbility extends TriggeredAbilityImpl { - - VenerableWarsingerTriggeredAbility() { - super(Zone.BATTLEFIELD, new ReturnFromGraveyardToBattlefieldTargetEffect(), true); - } - - private VenerableWarsingerTriggeredAbility(final VenerableWarsingerTriggeredAbility ability) { - super(ability); - } - - @Override - public VenerableWarsingerTriggeredAbility copy() { - return new VenerableWarsingerTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PLAYER; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - Player player = game.getPlayer(event.getPlayerId()); - if (player == null - || !event.getSourceId().equals(getSourceId()) - || !((DamagedEvent) event).isCombatDamage()) { - return false; - } - FilterCard filter = new FilterCreatureCard( - "creature card with mana value " + event.getAmount() + " less from your graveyard" - ); - filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, event.getAmount() + 1)); - this.getTargets().clear(); - this.addTarget(new TargetCardInYourGraveyard(filter)); - return true; - } - - @Override - public String getRule() { - return "Whenever {this} deals combat damage to a player, you may return target creature card " + - "with mana value X or less from your graveyard to the battlefield, " + - "where X is the amount of damage {this} dealt to that player."; - } -} diff --git a/Mage.Sets/src/mage/cards/w/WebstrikeElite.java b/Mage.Sets/src/mage/cards/w/WebstrikeElite.java index bc5997fa765..99d18e79cc9 100644 --- a/Mage.Sets/src/mage/cards/w/WebstrikeElite.java +++ b/Mage.Sets/src/mage/cards/w/WebstrikeElite.java @@ -1,9 +1,10 @@ package mage.cards.w; import mage.MageInt; -import mage.abilities.common.ZoneChangeTriggeredAbility; +import mage.abilities.Ability; +import mage.abilities.common.CycleTriggeredAbility; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.dynamicvalue.common.GetXValue; +import mage.abilities.dynamicvalue.common.EffectKeyValue; import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.keyword.CyclingAbility; import mage.abilities.keyword.ReachAbility; @@ -12,14 +13,10 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.ComparisonType; import mage.constants.SubType; -import mage.constants.Zone; import mage.filter.FilterPermanent; import mage.filter.common.FilterArtifactOrEnchantmentPermanent; -import mage.filter.predicate.mageobject.ManaValuePredicate; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.stack.StackObject; import mage.target.TargetPermanent; +import mage.target.targetadjustment.ManaValueTargetAdjuster; import java.util.UUID; @@ -27,6 +24,7 @@ import java.util.UUID; * @author TheElk801 */ public final class WebstrikeElite extends CardImpl { + FilterPermanent filter = new FilterArtifactOrEnchantmentPermanent("artifact or enchantment with mana value X"); public WebstrikeElite(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{G}"); @@ -43,7 +41,10 @@ public final class WebstrikeElite extends CardImpl { this.addAbility(new CyclingAbility(new ManaCostsImpl<>("{X}{G}{G}"))); // When you cycle this card, destroy up to one target artifact or enchantment with mana value X. - this.addAbility(new WebstrikeEliteTriggeredAbility()); + Ability ability = new CycleTriggeredAbility(new DestroyTargetEffect()); + ability.addTarget(new TargetPermanent(0, 1, filter)); + ability.setTargetAdjuster(new ManaValueTargetAdjuster(new EffectKeyValue("cycleXValue"), ComparisonType.EQUAL_TO)); + this.addAbility(ability); } private WebstrikeElite(final WebstrikeElite card) { @@ -55,47 +56,3 @@ public final class WebstrikeElite extends CardImpl { return new WebstrikeElite(this); } } - -class WebstrikeEliteTriggeredAbility extends ZoneChangeTriggeredAbility { - - WebstrikeEliteTriggeredAbility() { - super(Zone.ALL, new DestroyTargetEffect(), "", false); - } - - private WebstrikeEliteTriggeredAbility(final WebstrikeEliteTriggeredAbility ability) { - super(ability); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ACTIVATED_ABILITY; - } - - @Override - public WebstrikeEliteTriggeredAbility copy() { - return new WebstrikeEliteTriggeredAbility(this); - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (!event.getSourceId().equals(this.getSourceId())) { - return false; - } - StackObject object = game.getStack().getStackObject(event.getSourceId()); - if (object == null || !(object.getStackAbility() instanceof CyclingAbility)) { - return false; - } - FilterPermanent filter = new FilterArtifactOrEnchantmentPermanent("artifact or enchantment with mana value X"); - filter.add(new ManaValuePredicate( - ComparisonType.EQUAL_TO, GetXValue.instance.calculate(game, object.getStackAbility(), null) - )); - this.getTargets().clear(); - this.addTarget(new TargetPermanent(0, 1, filter)); - return true; - } - - @Override - public String getRule() { - return "When you cycle this card, destroy up to one target artifact or enchantment with mana value X."; - } -} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/BarracksOfTheThousandTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/BarracksOfTheThousandTest.java index e8f4545b1e4..c9838655cbe 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/BarracksOfTheThousandTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/BarracksOfTheThousandTest.java @@ -56,23 +56,29 @@ public class BarracksOfTheThousandTest extends CardTestPlayerBase { } @Test - public void trigger_onlyonce_doublemana() { + public void trigger_onceTwice_doublemana() { setStrictChooseMode(true); - addCard(Zone.BATTLEFIELD, playerB, "Heartbeat of Spring"); + addCard(Zone.BATTLEFIELD, playerA, "Mana Reflection"); addCard(Zone.HAND, playerA, "Armored Warhorse"); + addCard(Zone.HAND, playerA, "Savannah Lions", 2); initToTransform(); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Armored Warhorse"); + checkPermanentCount("One Gnome Soldier Token", 1, PhaseStep.BEGIN_COMBAT, playerA, "Gnome Soldier Token", 1); - setStopAt(1, PhaseStep.BEGIN_COMBAT); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Savannah Lions"); + waitStackResolved(3, PhaseStep.PRECOMBAT_MAIN); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Savannah Lions"); + + setStopAt(3, PhaseStep.BEGIN_COMBAT); execute(); - assertPermanentCount(playerA, "Gnome Soldier Token", 1); + assertPermanentCount(playerA, "Gnome Soldier Token", 3); assertPermanentCount(playerA, "Armored Warhorse", 1); + assertPermanentCount(playerA, "Savannah Lions", 2); } - @Test public void noTrigger_NotPaidWithBarrack() { setStrictChooseMode(true); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/TheMyriadPoolsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/TheMyriadPoolsTest.java new file mode 100644 index 00000000000..573ee3a2d44 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/TheMyriadPoolsTest.java @@ -0,0 +1,27 @@ +package org.mage.test.cards.single.lci; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class TheMyriadPoolsTest extends CardTestPlayerBase { + + @Test + public void castCopiesCorrectly() { + addCard(Zone.BATTLEFIELD, playerA, "The Myriad Pools"); + addCard(Zone.BATTLEFIELD, playerA, "Memnite"); + addCard(Zone.HAND, playerA, "Flying Men"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Flying Men"); + addTarget(playerA, "Memnite"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + setStrictChooseMode(true); + execute(); + + assertPermanentCount(playerA, "The Myriad Pools", 1); + assertPermanentCount(playerA, "Memnite", 0); + assertPermanentCount(playerA, "Flying Men", 2); + } +} diff --git a/Mage/src/main/java/mage/abilities/common/CastSpellPaidBySourceTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/CastSpellPaidBySourceTriggeredAbility.java index 501f68e9c74..058440e135e 100644 --- a/Mage/src/main/java/mage/abilities/common/CastSpellPaidBySourceTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/CastSpellPaidBySourceTriggeredAbility.java @@ -71,7 +71,8 @@ public class CastSpellPaidBySourceTriggeredAbility extends TriggeredAbilityImpl if (setTargetPointer) { this.getAllEffects().setTargetPointer(new FixedTarget(spell.getId(), game)); } + this.getEffects().setValue("spellCast", spell); return true; } -} \ No newline at end of file +}