From 784a5fb1e437d7159cc7b9d28c5d407bdb7c9aaf Mon Sep 17 00:00:00 2001 From: Matthew Wilson Date: Sat, 20 Jan 2024 20:20:17 +0200 Subject: [PATCH] Fix Sanctuary Blade ability causing a trigger (#11682) * Fix Sanctuary Blade ability causing a trigger * Remove unnecessary logic and correct test * Re-add strict choose mode --------- Co-authored-by: Matthew Wilson --- .../src/mage/cards/s/SanctuaryBlade.java | 6 +- .../equipped/AsBecomesAttachedTest.java | 56 ++++++++++++ ...ecomesAttachedToCreatureSourceAbility.java | 52 +++++++++++ ...BecomesAttachedToCreatureSourceEffect.java | 86 +++++++++++++++++++ 4 files changed, 197 insertions(+), 3 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/abilities/equipped/AsBecomesAttachedTest.java create mode 100644 Mage/src/main/java/mage/abilities/common/AsBecomesAttachedToCreatureSourceAbility.java create mode 100644 Mage/src/main/java/mage/abilities/effects/BecomesAttachedToCreatureSourceEffect.java diff --git a/Mage.Sets/src/mage/cards/s/SanctuaryBlade.java b/Mage.Sets/src/mage/cards/s/SanctuaryBlade.java index 0f86849a2e9..f75e2ff4e38 100644 --- a/Mage.Sets/src/mage/cards/s/SanctuaryBlade.java +++ b/Mage.Sets/src/mage/cards/s/SanctuaryBlade.java @@ -1,6 +1,6 @@ package mage.cards.s; -import mage.abilities.common.AttachedToCreatureSourceTriggeredAbility; +import mage.abilities.common.AsBecomesAttachedToCreatureSourceAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.Effect; @@ -25,11 +25,11 @@ public final class SanctuaryBlade extends CardImpl { public SanctuaryBlade(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); - + this.subtype.add(SubType.EQUIPMENT); // As Sanctuary Blade becomes attached to a creature, choose a color. - this.addAbility(new AttachedToCreatureSourceTriggeredAbility(new ChooseColorEffect(Outcome.Benefit), false)); + this.addAbility(new AsBecomesAttachedToCreatureSourceAbility(new ChooseColorEffect(Outcome.Benefit), "choose a color.")); // Equipped creature gets +2/+0 and has protection from the last chosen color. Effect boostEffect = new BoostEquippedEffect(2, 0); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/equipped/AsBecomesAttachedTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/equipped/AsBecomesAttachedTest.java new file mode 100644 index 00000000000..00c8bebee35 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/equipped/AsBecomesAttachedTest.java @@ -0,0 +1,56 @@ +package org.mage.test.cards.abilities.equipped; + +import mage.ObjectColor; +import mage.abilities.keyword.ProtectionAbility; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * Tests that the wording "as {this} becomes equipped to a creature..." is working correctly. + * As per rules 603.6d and 614.1c, these should be treated as a static ability with a + * replacement effect. They should NOT cause a trigger to be put on the stack. + * + * @author DominionSpy + */ +public class AsBecomesAttachedTest extends CardTestPlayerBase { + + /** + * Sanctuary Blade {2} + * Artifact - Equipment + * As Sanctuary Blade becomes attached to a creature, choose a color. + * Equipped creature gets +2/+0 and has protection from the last chosen color. + * Equip {3} + */ + @Test + public void test_SanctuaryBladeAbility() { + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2 + 2); + addCard(Zone.BATTLEFIELD, playerA, "Sanctuary Blade"); + addCard(Zone.BATTLEFIELD, playerA, "Llanowar Elves"); + addCard(Zone.BATTLEFIELD, playerA, "Elvish Mystic"); + + // As Sanctuary Blade becomes attached to a creature, choose a color. + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip", "Llanowar Elves"); + setChoice(playerA, "White"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 1); + + // Check that there is no trigger on the stack after the equip ability has resolved + checkStackSize("stack is empty", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 0); + checkAbility("llanowar elves must have protection", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Llanowar Elves", ProtectionAbility.class, true); + + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Equip", "Elvish Mystic"); + setChoice(playerA, "Blue"); + waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN, 1); + + // Do the same check for switching from one creature to another + checkStackSize("stack is empty", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, 0); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertAbility(playerA, "Llanowar Elves", ProtectionAbility.from(ObjectColor.WHITE), false); + assertAbility(playerA, "Elvish Mystic", ProtectionAbility.from(ObjectColor.BLUE), true); + } +} diff --git a/Mage/src/main/java/mage/abilities/common/AsBecomesAttachedToCreatureSourceAbility.java b/Mage/src/main/java/mage/abilities/common/AsBecomesAttachedToCreatureSourceAbility.java new file mode 100644 index 00000000000..f22b09fcb2b --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/AsBecomesAttachedToCreatureSourceAbility.java @@ -0,0 +1,52 @@ +package mage.abilities.common; + +import mage.abilities.StaticAbility; +import mage.abilities.effects.BecomesAttachedToCreatureSourceEffect; +import mage.abilities.effects.Effect; +import mage.constants.Zone; + +/** + * Based on {@link mage.abilities.common.AsEntersBattlefieldAbility}. + * This allows rule wording such as "as {this} becomes attached to a creature..." + * For this, there should not be a trigger, as in the case of the wording "when..." + * As per rule 603.6d, this should be a static ability. + * See [[Sanctuary Blade]]. + * + * @author DominionSpy + */ +public class AsBecomesAttachedToCreatureSourceAbility extends StaticAbility { + + public AsBecomesAttachedToCreatureSourceAbility(Effect effect) { + this(effect, null); + } + + public AsBecomesAttachedToCreatureSourceAbility(Effect effect, String text) { + super(Zone.BATTLEFIELD, new BecomesAttachedToCreatureSourceEffect(effect, null, text)); + } + + protected AsBecomesAttachedToCreatureSourceAbility(final AsBecomesAttachedToCreatureSourceAbility ability) { + super(ability); + } + + @Override + public AsBecomesAttachedToCreatureSourceAbility copy() { + return new AsBecomesAttachedToCreatureSourceAbility(this); + } + + @Override + public void addEffect(Effect effect) { + if (!getEffects().isEmpty()) { + Effect attachEffect = this.getEffects().get(0); + if (attachEffect instanceof BecomesAttachedToCreatureSourceEffect) { + ((BecomesAttachedToCreatureSourceEffect) attachEffect).addEffect(effect); + return; + } + } + super.addEffect(effect); + } + + @Override + public String getRule() { + return "As {this} becomes attached to a creature, " + super.getRule(); + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/BecomesAttachedToCreatureSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/BecomesAttachedToCreatureSourceEffect.java new file mode 100644 index 00000000000..db9b8ece34d --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/BecomesAttachedToCreatureSourceEffect.java @@ -0,0 +1,86 @@ +package mage.abilities.effects; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.condition.Condition; +import mage.constants.Duration; +import mage.game.Game; +import mage.game.events.GameEvent; + +/** + * Based on {@link EntersBattlefieldEffect}. + * This allows rule wording such as "as {this} becomes attached to a creature..." + * For this, there should not be a trigger, as in the case of the wording "when..." + * As per rule 614.1c, this should be a replacement effect. + * See [[Sanctuary Blade]]. + * + * @author DominionSpy + */ +public class BecomesAttachedToCreatureSourceEffect extends ReplacementEffectImpl { + + protected Effects baseEffects = new Effects(); + protected String text; + protected Condition condition; + + public BecomesAttachedToCreatureSourceEffect(Effect baseEffect) { + this(baseEffect, ""); + } + + public BecomesAttachedToCreatureSourceEffect(Effect baseEffect, String text) { + this(baseEffect, null, text); + } + + public BecomesAttachedToCreatureSourceEffect(Effect baseEffect, Condition condition, String text) { + super(Duration.WhileOnBattlefield, baseEffect.getOutcome(), false); + this.baseEffects.add(baseEffect); + this.text = text; + this.condition = condition; + } + + protected BecomesAttachedToCreatureSourceEffect(final BecomesAttachedToCreatureSourceEffect effect) { + super(effect); + this.baseEffects = effect.baseEffects.copy(); + this.text = effect.text; + this.condition = effect.condition; + } + + @Override + public BecomesAttachedToCreatureSourceEffect copy() { + return new BecomesAttachedToCreatureSourceEffect(this); + } + + public void addEffect(Effect effect) { + baseEffects.add(effect); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return GameEvent.EventType.ATTACH == event.getType(); + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + if (event.getSourceId().equals(source.getSourceId())) { + return condition == null || condition.apply(game, source); + } + return false; + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + for (Effect effect : baseEffects) { + if (effect instanceof ContinuousEffect) { + game.addEffect((ContinuousEffect) effect, source); + } else { + effect.setValue("appliedEffects", event.getAppliedEffects()); + effect.apply(game, source); + } + } + return false; + } + + @Override + public String getText(Mode mode) { + return (text == null || text.isEmpty()) ? baseEffects.getText(mode) : text; + } +}