From 59929d28606adae2b1df5b9c484beecc2a8a83c1 Mon Sep 17 00:00:00 2001 From: xenohedron Date: Fri, 20 Oct 2023 21:30:24 -0400 Subject: [PATCH] new SourceDealsDamageToYouTriggeredAbility fix #11262 test coverage provided by FlamebladeAngelTest --- Mage.Sets/src/mage/cards/e/EleshNorn.java | 64 +++---------- Mage.Sets/src/mage/cards/f/FarsightMask.java | 61 ++---------- .../src/mage/cards/f/FlamebladeAngel.java | 65 ++----------- .../mage/cards/m/MichikoKondaTruthSeeker.java | 53 +---------- .../src/mage/cards/r/RetaliatorGriffin.java | 92 ++----------------- ...ourceDealsDamageToYouTriggeredAbility.java | 91 ++++++++++++++++++ Mage/src/main/java/mage/util/CardUtil.java | 3 + 7 files changed, 135 insertions(+), 294 deletions(-) create mode 100644 Mage/src/main/java/mage/abilities/common/SourceDealsDamageToYouTriggeredAbility.java diff --git a/Mage.Sets/src/mage/cards/e/EleshNorn.java b/Mage.Sets/src/mage/cards/e/EleshNorn.java index 4037b65c292..601acc523b0 100644 --- a/Mage.Sets/src/mage/cards/e/EleshNorn.java +++ b/Mage.Sets/src/mage/cards/e/EleshNorn.java @@ -2,8 +2,8 @@ package mage.cards.e; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.SourceDealsDamageToYouTriggeredAbility; import mage.abilities.costs.Cost; import mage.abilities.costs.common.SacrificeTargetCost; import mage.abilities.costs.mana.GenericManaCost; @@ -15,14 +15,13 @@ import mage.abilities.keyword.VigilanceAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; +import mage.filter.StaticFilters; import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.game.Game; -import mage.game.events.GameEvent; import mage.players.Player; import mage.target.common.TargetControlledPermanent; -import mage.target.targetpointer.FixedTarget; import java.util.UUID; @@ -51,7 +50,7 @@ public final class EleshNorn extends CardImpl { this.addAbility(VigilanceAbility.getInstance()); // Whenever a source an opponent controls deals damage to you or a permanent you control, that source's controller loses 2 life unless they pay {1}. - this.addAbility(new EleshNornTriggeredAbility()); + this.addAbility(new SourceDealsDamageToYouTriggeredAbility(new EleshNornEffect(), StaticFilters.FILTER_PERMANENT, false)); // {2}{W}, Sacrifice three other creatures: Exile Elesh Norn, then return it to the battlefield transformed under its owner's control. Activate only as a sorcery. this.addAbility(new TransformAbility()); @@ -73,57 +72,11 @@ public final class EleshNorn extends CardImpl { } } -class EleshNornTriggeredAbility extends TriggeredAbilityImpl { - - EleshNornTriggeredAbility() { - super(Zone.BATTLEFIELD, new EleshNornEffect()); - } - - private EleshNornTriggeredAbility(final EleshNornTriggeredAbility ability) { - super(ability); - } - - @Override - public EleshNornTriggeredAbility copy() { - return new EleshNornTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - switch (event.getType()) { - case DAMAGED_PLAYER: - case DAMAGE_PERMANENT: - return true; - } - return false; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (!game.getOpponents(getControllerId()).contains(game.getControllerId(event.getSourceId()))) { - return false; - } - getEffects().setTargetPointer(new FixedTarget(game.getControllerId(event.getSourceId()))); - switch (event.getType()) { - case DAMAGED_PLAYER: - return isControlledBy(event.getTargetId()); - case DAMAGE_PERMANENT: - return isControlledBy(game.getControllerId(event.getTargetId())); - } - return false; - } - - @Override - public String getRule() { - return "Whenever a source an opponent controls deals damage to you or a permanent you control, " + - "that source's controller loses 2 life unless they pay {1}."; - } -} - class EleshNornEffect extends OneShotEffect { EleshNornEffect() { super(Outcome.Benefit); + staticText = "that source's controller loses 2 life unless they pay {1}"; } private EleshNornEffect(final EleshNornEffect effect) { @@ -138,10 +91,15 @@ class EleshNornEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); - Cost cost = new GenericManaCost(1); - if (player != null && cost.canPay(source, source, player.getId(), game) && player.chooseUse(Outcome.PreventDamage, "Pay {1}?", source, game) && cost.pay(source, game, source, player.getId(), false)) { + if (player == null) { return false; } + Cost cost = new GenericManaCost(1); + if (cost.canPay(source, source, player.getId(), game) + && player.chooseUse(Outcome.PreventDamage, "Pay {1}?", source, game) + && cost.pay(source, game, source, player.getId(), false)) { + return true; + } return player.loseLife(2, game, source, false) > 0; } } diff --git a/Mage.Sets/src/mage/cards/f/FarsightMask.java b/Mage.Sets/src/mage/cards/f/FarsightMask.java index fbfa525ef2a..16b3ee4835f 100644 --- a/Mage.Sets/src/mage/cards/f/FarsightMask.java +++ b/Mage.Sets/src/mage/cards/f/FarsightMask.java @@ -1,27 +1,29 @@ - package mage.cards.f; -import java.util.UUID; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SourceDealsDamageToYouTriggeredAbility; +import mage.abilities.condition.common.SourceTappedCondition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.common.DrawCardSourceControllerEffect; 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.permanent.Permanent; + +import java.util.UUID; /** * @author North */ public final class FarsightMask extends CardImpl { + private static final String rule = "Whenever a source an opponent controls deals damage to you, if {this} is untapped, you may draw a card."; + public FarsightMask(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{5}"); // Whenever a source an opponent controls deals damage to you, if Farsight Mask is untapped, you may draw a card. - this.addAbility(new FarsightMaskTriggeredAbility()); + this.addAbility(new ConditionalInterveningIfTriggeredAbility(new SourceDealsDamageToYouTriggeredAbility( + new DrawCardSourceControllerEffect(1), true + ), SourceTappedCondition.UNTAPPED, rule)); } private FarsightMask(final FarsightMask card) { @@ -33,46 +35,3 @@ public final class FarsightMask extends CardImpl { return new FarsightMask(this); } } - -class FarsightMaskTriggeredAbility extends TriggeredAbilityImpl { - - public FarsightMaskTriggeredAbility() { - super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1), true); - } - - private FarsightMaskTriggeredAbility(final FarsightMaskTriggeredAbility ability) { - super(ability); - } - - @Override - public FarsightMaskTriggeredAbility copy() { - return new FarsightMaskTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PLAYER; - } - - @Override - public boolean checkInterveningIfClause(Game game) { - Permanent permanent = game.getPermanent(getSourceId()); - return permanent != null && !permanent.isTapped(); - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (event.getTargetId().equals(controllerId)) { - UUID sourceControllerId = game.getControllerId(event.getSourceId()); - if (sourceControllerId != null && game.getOpponents(getControllerId()).contains(sourceControllerId)) { - return true; - } - } - return false; - } - - @Override - public String getRule() { - return "Whenever a source an opponent controls deals damage to you, if {this} is untapped, you may draw a card."; - } -} diff --git a/Mage.Sets/src/mage/cards/f/FlamebladeAngel.java b/Mage.Sets/src/mage/cards/f/FlamebladeAngel.java index 757a14b4133..2f7bc7c0015 100644 --- a/Mage.Sets/src/mage/cards/f/FlamebladeAngel.java +++ b/Mage.Sets/src/mage/cards/f/FlamebladeAngel.java @@ -1,9 +1,7 @@ - package mage.cards.f; -import java.util.UUID; import mage.MageInt; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SourceDealsDamageToYouTriggeredAbility; import mage.abilities.effects.Effect; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.keyword.FlyingAbility; @@ -11,11 +9,9 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; -import mage.target.targetpointer.FixedTarget; +import mage.filter.StaticFilters; + +import java.util.UUID; /** * @@ -32,7 +28,8 @@ public final class FlamebladeAngel extends CardImpl { // Flying this.addAbility(FlyingAbility.getInstance()); // Whenever a source an opponent controls deals damage to you or a permanent you control, you may have Flameblade Angel deal 1 damage to that source's controller. - this.addAbility(new FlamebladeAngelTriggeredAbility()); + Effect effect = new DamageTargetEffect(1, true, "that source's controller"); + this.addAbility(new SourceDealsDamageToYouTriggeredAbility(effect, StaticFilters.FILTER_PERMANENT, true)); } @@ -45,53 +42,3 @@ public final class FlamebladeAngel extends CardImpl { return new FlamebladeAngel(this); } } - -class FlamebladeAngelTriggeredAbility extends TriggeredAbilityImpl { - - public FlamebladeAngelTriggeredAbility() { - super(Zone.BATTLEFIELD, new DamageTargetEffect(1), true); - } - - private FlamebladeAngelTriggeredAbility(final FlamebladeAngelTriggeredAbility ability) { - super(ability); - } - - @Override - public FlamebladeAngelTriggeredAbility copy() { - return new FlamebladeAngelTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PERMANENT - || event.getType() == GameEvent.EventType.DAMAGED_PLAYER; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - boolean result = false; - UUID sourceControllerId = game.getControllerId(event.getSourceId()); - if (sourceControllerId != null && game.getOpponents(getControllerId()).contains(sourceControllerId)) { - - if (event.getTargetId().equals(getControllerId())) { - result = true; - } else { - Permanent permanent = game.getPermanentOrLKIBattlefield(event.getTargetId()); - if (permanent != null && isControlledBy(permanent.getControllerId())) { - result = true; - } - } - if (result) { - for (Effect effect : getEffects()) { - effect.setTargetPointer(new FixedTarget(sourceControllerId)); - } - } - } - return result; - } - - @Override - public String getRule() { - return "Whenever a source an opponent controls deals damage to you or a permanent you control, you may have {this} deal 1 damage to that source's controller."; - } -} diff --git a/Mage.Sets/src/mage/cards/m/MichikoKondaTruthSeeker.java b/Mage.Sets/src/mage/cards/m/MichikoKondaTruthSeeker.java index 9e736109727..118ce5484f6 100644 --- a/Mage.Sets/src/mage/cards/m/MichikoKondaTruthSeeker.java +++ b/Mage.Sets/src/mage/cards/m/MichikoKondaTruthSeeker.java @@ -1,20 +1,16 @@ - package mage.cards.m; -import java.util.UUID; import mage.MageInt; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SourceDealsDamageToYouTriggeredAbility; import mage.abilities.effects.common.SacrificeEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.SuperType; -import mage.constants.Zone; -import mage.filter.FilterPermanent; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.target.targetpointer.FixedTarget; +import mage.filter.StaticFilters; + +import java.util.UUID; /** * @@ -32,7 +28,7 @@ public final class MichikoKondaTruthSeeker extends CardImpl { this.toughness = new MageInt(2); // Whenever a source an opponent controls deals damage to you, that player sacrifices a permanent. - this.addAbility(new MichikoKondaTruthSeekerAbility()); + this.addAbility(new SourceDealsDamageToYouTriggeredAbility(new SacrificeEffect(StaticFilters.FILTER_PERMANENT_A, 1, "that player"), false)); } private MichikoKondaTruthSeeker(final MichikoKondaTruthSeeker card) { @@ -44,42 +40,3 @@ public final class MichikoKondaTruthSeeker extends CardImpl { return new MichikoKondaTruthSeeker(this); } } - -class MichikoKondaTruthSeekerAbility extends TriggeredAbilityImpl { - - public MichikoKondaTruthSeekerAbility() { - super(Zone.BATTLEFIELD, new SacrificeEffect(new FilterPermanent(), 1, "that player"), false); - } - - private MichikoKondaTruthSeekerAbility(final MichikoKondaTruthSeekerAbility ability) { - super(ability); - } - - @Override - public MichikoKondaTruthSeekerAbility copy() { - return new MichikoKondaTruthSeekerAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PLAYER; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (event.getTargetId().equals(getControllerId())) { - UUID sourceControllerId = game.getControllerId(event.getSourceId()); - if (sourceControllerId != null && - game.getOpponents(getControllerId()).contains(sourceControllerId)) { - getEffects().get(0).setTargetPointer(new FixedTarget(sourceControllerId)); - return true; - } - } - return false; - } - - @Override - public String getRule() { - return "Whenever a source an opponent controls deals damage to you, that player sacrifices a permanent."; - } -} diff --git a/Mage.Sets/src/mage/cards/r/RetaliatorGriffin.java b/Mage.Sets/src/mage/cards/r/RetaliatorGriffin.java index d6ab8f95871..f63b294273d 100644 --- a/Mage.Sets/src/mage/cards/r/RetaliatorGriffin.java +++ b/Mage.Sets/src/mage/cards/r/RetaliatorGriffin.java @@ -1,28 +1,20 @@ - package mage.cards.r; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.common.SourceDealsDamageToYouTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Outcome; -import mage.constants.Zone; import mage.counters.CounterType; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; -import mage.players.Player; + +import java.util.UUID; /** - * - * @author North + * @author xenohedron */ public final class RetaliatorGriffin extends CardImpl { @@ -34,8 +26,11 @@ public final class RetaliatorGriffin extends CardImpl { this.toughness = new MageInt(2); this.addAbility(FlyingAbility.getInstance()); + // Whenever a source an opponent controls deals damage to you, you may put that many +1/+1 counters on Retaliator Griffin. - this.addAbility(new RetaliatorGriffinTriggeredAbility()); + this.addAbility(new SourceDealsDamageToYouTriggeredAbility(new AddCountersSourceEffect( + CounterType.P1P1.createInstance(), SavedDamageValue.MANY, true + ), true)); } private RetaliatorGriffin(final RetaliatorGriffin card) { @@ -47,72 +42,3 @@ public final class RetaliatorGriffin extends CardImpl { return new RetaliatorGriffin(this); } } - -class RetaliatorGriffinTriggeredAbility extends TriggeredAbilityImpl { - - public RetaliatorGriffinTriggeredAbility() { - super(Zone.BATTLEFIELD, new RetaliatorGriffinEffect(), true); - } - - private RetaliatorGriffinTriggeredAbility(final RetaliatorGriffinTriggeredAbility ability) { - super(ability); - } - - @Override - public RetaliatorGriffinTriggeredAbility copy() { - return new RetaliatorGriffinTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PLAYER; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (event.getTargetId().equals(getControllerId())) { - UUID sourceControllerId = game.getControllerId(event.getSourceId()); - if (sourceControllerId != null && - game.getOpponents(getControllerId()).contains(sourceControllerId)) { - getEffects().get(0).setValue("damageAmount", event.getAmount()); - return true; - } - } - return false; - } - - @Override - public String getRule() { - return "Whenever a source an opponent controls deals damage to you, you may put that many +1/+1 counters on {this}."; - } -} - -class RetaliatorGriffinEffect extends OneShotEffect { - - public RetaliatorGriffinEffect() { - super(Outcome.BoostCreature); - } - - private RetaliatorGriffinEffect(final RetaliatorGriffinEffect effect) { - super(effect); - } - - @Override - public RetaliatorGriffinEffect copy() { - return new RetaliatorGriffinEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - Permanent permanent = game.getPermanent(source.getSourceId()); - Integer amount = (Integer) this.getValue("damageAmount"); - if (permanent != null && amount != null && amount > 0) { - new AddCountersSourceEffect(CounterType.P1P1.createInstance(amount), true).apply(game, source); - } - return true; - } - return false; - } -} diff --git a/Mage/src/main/java/mage/abilities/common/SourceDealsDamageToYouTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/SourceDealsDamageToYouTriggeredAbility.java new file mode 100644 index 00000000000..a321b698a75 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/SourceDealsDamageToYouTriggeredAbility.java @@ -0,0 +1,91 @@ +package mage.abilities.common; + +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.Effect; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.target.targetpointer.FixedTarget; + +/** + * @author xenohedron + */ +public class SourceDealsDamageToYouTriggeredAbility extends TriggeredAbilityImpl { + + private final FilterPermanent filter; + + /** + * Whenever a source an opponent controls deals damage to you, effect + */ + public SourceDealsDamageToYouTriggeredAbility(Effect effect, boolean optional) { + this(effect, null, optional); + } + + /** + * Whenever a source an opponent controls deals damage to you or a [filter] you control, effect + */ + public SourceDealsDamageToYouTriggeredAbility(Effect effect, FilterPermanent filter, boolean optional) { + super(Zone.BATTLEFIELD, effect, optional); + this.filter = filter; + if (filter != null) { + setTriggerPhrase("Whenever a source an opponent controls deals damage to you or a " + filter.getMessage() + " you control, "); + } else { + setTriggerPhrase("Whenever a source an opponent controls deals damage to you, "); + } + } + + protected SourceDealsDamageToYouTriggeredAbility(final SourceDealsDamageToYouTriggeredAbility ability) { + super(ability); + this.filter = ability.filter; + } + + @Override + public SourceDealsDamageToYouTriggeredAbility copy() { + return new SourceDealsDamageToYouTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + switch (event.getType()) { + case DAMAGED_PLAYER: + case DAMAGED_PERMANENT: + return true; + default: + return false; + } + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + switch (event.getType()) { + case DAMAGED_PLAYER: + if (!this.isControlledBy(event.getTargetId())) { + return false; + } + break; + case DAMAGED_PERMANENT: + if (filter == null) { + return false; + } + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getTargetId()); + if (permanent == null || !permanent.isControlledBy(this.getControllerId())) { + return false; + } + if (!filter.match(permanent, this.getControllerId(), this, game)) { + return false; + } + break; + default: + return false; + } + int damageAmount = event.getAmount(); + if (damageAmount < 1) { + return false; + } + this.getAllEffects().setValue("damage", damageAmount); + this.getAllEffects().setTargetPointer(new FixedTarget(game.getControllerId(event.getSourceId()))); + return true; + } +} diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index 89d8605e28e..4a594234fc4 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -12,6 +12,7 @@ import mage.abilities.costs.Costs; import mage.abilities.costs.VariableCost; import mage.abilities.costs.mana.*; import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.SavedDamageValue; import mage.abilities.dynamicvalue.common.StaticValue; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.Effect; @@ -909,6 +910,8 @@ public final class CardUtil { boolean xValue = amount.toString().equals("X"); if (xValue) { sb.append("X ").append(counter.getName()).append(" counters"); + } else if (amount == SavedDamageValue.MANY) { + sb.append("that many ").append(counter.getName()).append(" counters"); } else { sb.append(counter.getDescription()); }