From 43727e2dd2e9d89ae74b3bb5fe9a797272a50022 Mon Sep 17 00:00:00 2001 From: Susucre <34709007+Susucre@users.noreply.github.com> Date: Mon, 14 Aug 2023 14:25:08 +0200 Subject: [PATCH 1/2] Rework Whipgrass Entangler Made a class for "Ability linked with an Effect", that also takes responsability of manually calling its effect's newId method. --- .../src/mage/cards/w/WhipgrassEntangler.java | 42 +++-- .../single/lgn/WhipgrassEntanglerTest.java | 146 ++++++++++++++++++ .../common/LinkedSimpleStaticAbility.java | 82 ++++++++++ ...CantAttackBlockUnlessPaysSourceEffect.java | 3 +- .../continuous/GainAbilityTargetEffect.java | 17 +- 5 files changed, 271 insertions(+), 19 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/lgn/WhipgrassEntanglerTest.java create mode 100644 Mage/src/main/java/mage/abilities/common/LinkedSimpleStaticAbility.java diff --git a/Mage.Sets/src/mage/cards/w/WhipgrassEntangler.java b/Mage.Sets/src/mage/cards/w/WhipgrassEntangler.java index 1d84afd6fbd8..038a3d096ef7 100644 --- a/Mage.Sets/src/mage/cards/w/WhipgrassEntangler.java +++ b/Mage.Sets/src/mage/cards/w/WhipgrassEntangler.java @@ -1,21 +1,20 @@ package mage.cards.w; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; +import mage.abilities.common.LinkedSimpleStaticAbility; import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.ManaCosts; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; -import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; import mage.abilities.effects.common.combat.CantAttackBlockUnlessPaysSourceEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Duration; +import mage.constants.SubType; import mage.constants.Zone; import mage.filter.FilterPermanent; import mage.game.Game; @@ -23,23 +22,24 @@ import mage.game.permanent.Permanent; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** - * - * @author emerald000 & L_J + * @author Susucr */ public final class WhipgrassEntangler extends CardImpl { public WhipgrassEntangler(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.CLERIC); this.power = new MageInt(1); this.toughness = new MageInt(3); // {1}{W}: Until end of turn, target creature gains "This creature can't attack or block unless its controller pays {1} for each Cleric on the battlefield." - Ability abilityToGain = new SimpleStaticAbility(Zone.BATTLEFIELD, new WhipgrassEntanglerCantAttackUnlessYouPayEffect()); - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, - new GainAbilityTargetEffect(abilityToGain, Duration.EndOfTurn).setText("Until end of turn, target creature gains \"This creature can't attack or block unless its controller pays {1} for each Cleric on the battlefield.\""), + Ability gainedAbility = new LinkedSimpleStaticAbility(new WhipgrassEntanglerCantAttackUnlessYouPayEffect()); + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, + new GainAbilityTargetEffect(gainedAbility, Duration.EndOfTurn).setText("Until end of turn, target creature gains \"This creature can't attack or block unless its controller pays {1} for each Cleric on the battlefield.\""), new ManaCostsImpl<>("{1}{W}")); ability.addTarget(new TargetCreaturePermanent()); this.addAbility(ability); @@ -56,9 +56,10 @@ public WhipgrassEntangler copy() { } } -class WhipgrassEntanglerCantAttackUnlessYouPayEffect extends CantAttackBlockUnlessPaysSourceEffect { +class WhipgrassEntanglerCantAttackUnlessYouPayEffect extends CantAttackBlockUnlessPaysSourceEffect implements LinkedSimpleStaticAbility.ChildEffect { private static final FilterPermanent filter = new FilterPermanent("Cleric on the battlefield"); + private UUID parentLinkHandshake = null; static { filter.add(SubType.CLERIC.getPredicate()); @@ -69,13 +70,16 @@ class WhipgrassEntanglerCantAttackUnlessYouPayEffect extends CantAttackBlockUnle staticText = "This creature can't attack or block unless its controller pays {1} for each Cleric on the battlefield"; } - WhipgrassEntanglerCantAttackUnlessYouPayEffect(WhipgrassEntanglerCantAttackUnlessYouPayEffect effect) { + private WhipgrassEntanglerCantAttackUnlessYouPayEffect(final WhipgrassEntanglerCantAttackUnlessYouPayEffect effect) { super(effect); + this.parentLinkHandshake = effect.parentLinkHandshake; } @Override public boolean applies(GameEvent event, Ability source, Game game) { - return source.getSourceId().equals(event.getSourceId()); + return source.getSourceId().equals(event.getSourceId()) + && source instanceof LinkedSimpleStaticAbility + && ((LinkedSimpleStaticAbility) source).checkLinked(this.parentLinkHandshake); } @Override @@ -95,4 +99,16 @@ public WhipgrassEntanglerCantAttackUnlessYouPayEffect copy() { return new WhipgrassEntanglerCantAttackUnlessYouPayEffect(this); } + @Override + public void newId() { + // No id set this way. Parent Ability takes responsability of calling manualNewId() + } + + public void manualNewId() { + super.newId(); + } + + public void setParentLinkHandshake(UUID parentLinkHandshake) { + this.parentLinkHandshake = parentLinkHandshake; + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/lgn/WhipgrassEntanglerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/lgn/WhipgrassEntanglerTest.java new file mode 100644 index 000000000000..f51b9b70d48e --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/lgn/WhipgrassEntanglerTest.java @@ -0,0 +1,146 @@ +package org.mage.test.cards.single.lgn; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class WhipgrassEntanglerTest extends CardTestPlayerBase { + + /** + * Whipgrass Entangler + * {2}{W} + * Creature — Human Cleric + *
+ * {1}{W}: Until end of turn, target creature gains "This creature can't attack or block unless its controller pays {1} for each Cleric on the battlefield." + *
+ * 1/3 + */ + private static final String entangler = "Whipgrass Entangler"; + + @Test + public void gainAbilityHimselfAndPay() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, entangler); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 20); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{W}: Until end of turn, ", entangler); + attack(1, playerA, entangler); + setChoice(playerA, true); // yes to "pay {1} for Entangler to attack" + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20 - 1); + assertTappedCount("Plains", true, 2 * 1 + 1 * 1); + } + + @Test + public void gainAbilityTwiceAndPay() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, entangler); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 20); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{W}: Until end of turn, ", entangler); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{W}: Until end of turn, ", entangler); + attack(1, playerA, entangler); + setChoice(playerA, "Whipgrass Entangler"); // two of the same replacement effect to choose from. + setChoice(playerA, true); // yes to "pay {1} for Entangler to attack" + setChoice(playerA, true); // yes to "pay {1} for Entangler to attack" + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20 - 1); + assertTappedCount("Plains", true, 2 * 2 + 1 * 2); + } + + @Test + public void gainAbilityWithTwoClericAndPay() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, entangler); + addCard(Zone.BATTLEFIELD, playerA, "Akroma's Devoted"); // 2/4 Cleric + addCard(Zone.BATTLEFIELD, playerA, "Plains", 20); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{W}: Until end of turn, ", entangler); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{W}: Until end of turn, ", "Akroma's Devoted"); + attack(1, playerA, entangler); + setChoice(playerA, true); // yes to "pay {2} for Entangler to attack" + attack(1, playerA, "Akroma's Devoted"); + setChoice(playerA, true); // yes to "pay {2} for Akroma's Devoted to attack" + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20 - 1 - 2); + assertTappedCount("Plains", true, 2 * 2 + 2 * 2); + } + + @Test + public void gainAbilityFromTwoEntanglerAndPay() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, "Akroma's Devoted"); // 2/4 Cleric + addCard(Zone.BATTLEFIELD, playerA, entangler, 1); + + // In order to make sure the ability is activated from both entanglers, we exile one and play the second one after + addCard(Zone.HAND, playerA, entangler); + addCard(Zone.HAND, playerA, "Swords to Plowshares", 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 20); + + // Activate first entangler + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{W}: Until end of turn, ", "Akroma's Devoted"); + // Exile first entangler + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Swords to Plowshares", entangler, true); + + // Cast second entangler + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, entangler, true); + // Activate second entangler + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{W}: Until end of turn, ", "Akroma's Devoted"); + + attack(1, playerA, "Akroma's Devoted"); + setChoice(playerA, "Akroma's Devoted"); // two of the same replacement effect to choose from. + setChoice(playerA, true); // yes to "pay {2} for Akroma's Devoted to attack" + setChoice(playerA, true); // yes to "pay {2} for Akroma's Devoted to attack" + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20 - 2); + assertTappedCount("Plains", true, 2 * 2 + 2 * 2 + 3 + 1); + } + + @Test + public void gainAbilityWithCloneAndPay() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, "Akroma's Devoted"); // 2/4 Cleric + addCard(Zone.BATTLEFIELD, playerA, entangler); + addCard(Zone.HAND, playerA, "Sakashima the Impostor"); // Clone with different name for easy targets + + // In order to make sure the ability is activated from the clone, we exile the original one before activating Sakashima + addCard(Zone.HAND, playerA, "Swords to Plowshares", 1); + addCard(Zone.BATTLEFIELD, playerA, "Tundra", 20); + + // Activate the original Entangler + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{W}: Until end of turn, ", "Akroma's Devoted"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + // Cast Sakashima + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sakashima the Impostor", true); + setChoice(playerA, true); // yes to clone. + setChoice(playerA, entangler); // copy entangler + // Exile original entangler + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Swords to Plowshares", entangler, true); + // Activate Sakashima. + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{W}: Until end of turn, ", "Akroma's Devoted"); + + attack(1, playerA, "Akroma's Devoted"); + setChoice(playerA, "Akroma's Devoted"); // two of the same replacement effect to choose from. + setChoice(playerA, true); // yes to "pay {2} for Akroma's Devoted to attack" + setChoice(playerA, true); // yes to "pay {2} for Akroma's Devoted to attack" + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, 20 - 2); + assertTappedCount("Tundra", true, 2 * 2 + 2 * 2 + 4 + 1); + } +} diff --git a/Mage/src/main/java/mage/abilities/common/LinkedSimpleStaticAbility.java b/Mage/src/main/java/mage/abilities/common/LinkedSimpleStaticAbility.java new file mode 100644 index 000000000000..9a6691701f53 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/LinkedSimpleStaticAbility.java @@ -0,0 +1,82 @@ +package mage.abilities.common; + +import mage.abilities.effects.Effect; +import mage.constants.Zone; +import mage.util.CardUtil; + +import java.util.Objects; +import java.util.UUID; + +/** + * Warning: please test with a lot of care when using this class for new things. + *
+ * A static Ability linked to an Effect. + * The parent Ability does take responsability of setting the id for the child. + * + * @author Susucr + */ +public class LinkedSimpleStaticAbility extends SimpleStaticAbility { + + public interface ChildEffect extends Effect { + /** + * Set the link for the child. + */ + void setParentLinkHandshake(UUID parentLinkHandshake); + + /** + * The child Id should only change on copy when the parent wants it to. + */ + void manualNewId(); + } + + + /** + * The handshake UUID between this parent ability and its child. + */ + private UUID linkedHandshake; + + public LinkedSimpleStaticAbility(ChildEffect effect) { + this(Zone.BATTLEFIELD, effect); + } + + public LinkedSimpleStaticAbility(Zone zone, ChildEffect effect) { + super(Zone.BATTLEFIELD, effect); + this.linkedHandshake = UUID.randomUUID(); + initHandshake(); + setEffectIdManually(); + } + + private LinkedSimpleStaticAbility(final LinkedSimpleStaticAbility effect) { + super(effect); + this.linkedHandshake = UUID.randomUUID(); + initHandshake(); + } + + @Override + public LinkedSimpleStaticAbility copy() { + return new LinkedSimpleStaticAbility(this); + } + + private void initHandshake() { + this.linkedHandshake = UUID.randomUUID(); + CardUtil.castStream(this.getEffects().stream(), ChildEffect.class) + .filter(Objects::nonNull) + .forEach(e -> e.setParentLinkHandshake(linkedHandshake)); + } + + public void setEffectIdManually() { + CardUtil.castStream(this.getEffects().stream(), ChildEffect.class) + .filter(Objects::nonNull) + .forEach(e -> e.manualNewId()); + } + + public boolean checkLinked(UUID handshake) { + return linkedHandshake.equals(handshake); + } + + @Override + public void newId() { + super.newId(); + } +} + diff --git a/Mage/src/main/java/mage/abilities/effects/common/combat/CantAttackBlockUnlessPaysSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/combat/CantAttackBlockUnlessPaysSourceEffect.java index dde923561663..235acfa04846 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/combat/CantAttackBlockUnlessPaysSourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/combat/CantAttackBlockUnlessPaysSourceEffect.java @@ -8,7 +8,6 @@ import mage.constants.Outcome; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; /** * @author LevelX2 @@ -26,7 +25,7 @@ public CantAttackBlockUnlessPaysSourceEffect(ManaCosts manaCosts, RestrictType r staticText = "{this} can't " + restrictType.toString() + " unless you pay " + (manaCosts == null ? "" : manaCosts.getText()); } - public CantAttackBlockUnlessPaysSourceEffect(CantAttackBlockUnlessPaysSourceEffect effect) { + protected CantAttackBlockUnlessPaysSourceEffect(final CantAttackBlockUnlessPaysSourceEffect effect) { super(effect); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityTargetEffect.java index f5aabf3a5f8d..385859aaaf29 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityTargetEffect.java @@ -3,13 +3,12 @@ import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.Mode; +import mage.abilities.common.LinkedSimpleStaticAbility; import mage.abilities.effects.ContinuousEffectImpl; import mage.cards.Card; import mage.constants.*; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.target.Target; -import mage.util.CardUtil; import java.util.*; @@ -44,6 +43,8 @@ public GainAbilityTargetEffect(Ability ability, Duration duration, String rule) public GainAbilityTargetEffect(Ability ability, Duration duration, String rule, boolean useOnCard) { super(duration, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, ability.getEffects().getOutcome(ability, Outcome.AddAbility)); this.ability = ability; + this.ability = copyAbility(); + this.staticText = rule; this.useOnCard = useOnCard; @@ -52,8 +53,7 @@ public GainAbilityTargetEffect(Ability ability, Duration duration, String rule, protected GainAbilityTargetEffect(final GainAbilityTargetEffect effect) { super(effect); - this.ability = effect.ability.copy(); - this.ability.newId(); // This is needed if the effect is copied e.g. by a clone so the ability can be added multiple times to permanents + this.ability = effect.copyAbility(); this.useOnCard = effect.useOnCard; this.waitingCardPermanent = effect.waitingCardPermanent; this.durationPhaseStep = effect.durationPhaseStep; @@ -202,6 +202,15 @@ public boolean apply(Game game, Ability source) { return affectedTargets > 0; } + public Ability copyAbility() { + Ability ability = this.ability.copy(); + ability.newId(); + if (ability instanceof LinkedSimpleStaticAbility) { + ((LinkedSimpleStaticAbility) ability).setEffectIdManually(); + } + return ability; + } + @Override public String getText(Mode mode) { if (staticText != null && !staticText.isEmpty()) { From b67c5ea059f7d6dfc51d1657d11ea2034e53fa30 Mon Sep 17 00:00:00 2001 From: Susucre <34709007+Susucre@users.noreply.github.com> Date: Tue, 15 Aug 2023 10:17:57 +0200 Subject: [PATCH 2/2] apply review & cleanup --- .../src/mage/cards/w/WhipgrassEntangler.java | 10 ++++---- ....java => LinkedEffectIdStaticAbility.java} | 12 ++++----- .../continuous/GainAbilityTargetEffect.java | 25 ++++++++++++------- 3 files changed, 27 insertions(+), 20 deletions(-) rename Mage/src/main/java/mage/abilities/common/{LinkedSimpleStaticAbility.java => LinkedEffectIdStaticAbility.java} (82%) diff --git a/Mage.Sets/src/mage/cards/w/WhipgrassEntangler.java b/Mage.Sets/src/mage/cards/w/WhipgrassEntangler.java index 038a3d096ef7..817f6a72d399 100644 --- a/Mage.Sets/src/mage/cards/w/WhipgrassEntangler.java +++ b/Mage.Sets/src/mage/cards/w/WhipgrassEntangler.java @@ -3,7 +3,7 @@ import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.common.LinkedSimpleStaticAbility; +import mage.abilities.common.LinkedEffectIdStaticAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ManaCosts; import mage.abilities.costs.mana.ManaCostsImpl; @@ -37,7 +37,7 @@ public WhipgrassEntangler(UUID ownerId, CardSetInfo setInfo) { this.toughness = new MageInt(3); // {1}{W}: Until end of turn, target creature gains "This creature can't attack or block unless its controller pays {1} for each Cleric on the battlefield." - Ability gainedAbility = new LinkedSimpleStaticAbility(new WhipgrassEntanglerCantAttackUnlessYouPayEffect()); + Ability gainedAbility = new LinkedEffectIdStaticAbility(new WhipgrassEntanglerCantAttackUnlessYouPayEffect()); Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new GainAbilityTargetEffect(gainedAbility, Duration.EndOfTurn).setText("Until end of turn, target creature gains \"This creature can't attack or block unless its controller pays {1} for each Cleric on the battlefield.\""), new ManaCostsImpl<>("{1}{W}")); @@ -56,7 +56,7 @@ public WhipgrassEntangler copy() { } } -class WhipgrassEntanglerCantAttackUnlessYouPayEffect extends CantAttackBlockUnlessPaysSourceEffect implements LinkedSimpleStaticAbility.ChildEffect { +class WhipgrassEntanglerCantAttackUnlessYouPayEffect extends CantAttackBlockUnlessPaysSourceEffect implements LinkedEffectIdStaticAbility.ChildEffect { private static final FilterPermanent filter = new FilterPermanent("Cleric on the battlefield"); private UUID parentLinkHandshake = null; @@ -78,8 +78,8 @@ private WhipgrassEntanglerCantAttackUnlessYouPayEffect(final WhipgrassEntanglerC @Override public boolean applies(GameEvent event, Ability source, Game game) { return source.getSourceId().equals(event.getSourceId()) - && source instanceof LinkedSimpleStaticAbility - && ((LinkedSimpleStaticAbility) source).checkLinked(this.parentLinkHandshake); + && source instanceof LinkedEffectIdStaticAbility + && ((LinkedEffectIdStaticAbility) source).checkLinked(this.parentLinkHandshake); } @Override diff --git a/Mage/src/main/java/mage/abilities/common/LinkedSimpleStaticAbility.java b/Mage/src/main/java/mage/abilities/common/LinkedEffectIdStaticAbility.java similarity index 82% rename from Mage/src/main/java/mage/abilities/common/LinkedSimpleStaticAbility.java rename to Mage/src/main/java/mage/abilities/common/LinkedEffectIdStaticAbility.java index 9a6691701f53..81faea7d7d7a 100644 --- a/Mage/src/main/java/mage/abilities/common/LinkedSimpleStaticAbility.java +++ b/Mage/src/main/java/mage/abilities/common/LinkedEffectIdStaticAbility.java @@ -15,7 +15,7 @@ * * @author Susucr */ -public class LinkedSimpleStaticAbility extends SimpleStaticAbility { +public class LinkedEffectIdStaticAbility extends SimpleStaticAbility { public interface ChildEffect extends Effect { /** @@ -35,26 +35,26 @@ public interface ChildEffect extends Effect { */ private UUID linkedHandshake; - public LinkedSimpleStaticAbility(ChildEffect effect) { + public LinkedEffectIdStaticAbility(ChildEffect effect) { this(Zone.BATTLEFIELD, effect); } - public LinkedSimpleStaticAbility(Zone zone, ChildEffect effect) { + public LinkedEffectIdStaticAbility(Zone zone, ChildEffect effect) { super(Zone.BATTLEFIELD, effect); this.linkedHandshake = UUID.randomUUID(); initHandshake(); setEffectIdManually(); } - private LinkedSimpleStaticAbility(final LinkedSimpleStaticAbility effect) { + private LinkedEffectIdStaticAbility(final LinkedEffectIdStaticAbility effect) { super(effect); this.linkedHandshake = UUID.randomUUID(); initHandshake(); } @Override - public LinkedSimpleStaticAbility copy() { - return new LinkedSimpleStaticAbility(this); + public LinkedEffectIdStaticAbility copy() { + return new LinkedEffectIdStaticAbility(this); } private void initHandshake() { diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityTargetEffect.java index 385859aaaf29..e4780c8e425a 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityTargetEffect.java @@ -3,7 +3,7 @@ import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.Mode; -import mage.abilities.common.LinkedSimpleStaticAbility; +import mage.abilities.common.LinkedEffectIdStaticAbility; import mage.abilities.effects.ContinuousEffectImpl; import mage.cards.Card; import mage.constants.*; @@ -17,7 +17,7 @@ */ public class GainAbilityTargetEffect extends ContinuousEffectImpl { - protected Ability ability; + protected final Ability ability; // shall a card gain the ability (otherwise a permanent) private final boolean useOnCard; // only one card per ability supported @@ -42,8 +42,7 @@ public GainAbilityTargetEffect(Ability ability, Duration duration, String rule) public GainAbilityTargetEffect(Ability ability, Duration duration, String rule, boolean useOnCard) { super(duration, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, ability.getEffects().getOutcome(ability, Outcome.AddAbility)); - this.ability = ability; - this.ability = copyAbility(); + this.ability = copyAbility(ability); // See the method's comment, ability.copy() is not enough. this.staticText = rule; this.useOnCard = useOnCard; @@ -53,7 +52,7 @@ public GainAbilityTargetEffect(Ability ability, Duration duration, String rule, protected GainAbilityTargetEffect(final GainAbilityTargetEffect effect) { super(effect); - this.ability = effect.copyAbility(); + this.ability = copyAbility(effect.ability); // See the method's comment, ability.copy() is not enough. this.useOnCard = effect.useOnCard; this.waitingCardPermanent = effect.waitingCardPermanent; this.durationPhaseStep = effect.durationPhaseStep; @@ -202,11 +201,19 @@ public boolean apply(Game game, Ability source) { return affectedTargets > 0; } - public Ability copyAbility() { - Ability ability = this.ability.copy(); + /** + * Copying the ability and providing ability is needed in a few situations, + * The copy in order to have internal fields be proper to that ability in particular. + * Id must be different for the copy, for a few things like the GainAbilityTargetEffect gained + * by a clone, or in the case of an activated ability, called multiple times on the same target, + * and thus the ability should be gained multiple times. + */ + + private Ability copyAbility(Ability toCopyAbility) { + Ability ability = toCopyAbility.copy(); ability.newId(); - if (ability instanceof LinkedSimpleStaticAbility) { - ((LinkedSimpleStaticAbility) ability).setEffectIdManually(); + if (ability instanceof LinkedEffectIdStaticAbility) { + ((LinkedEffectIdStaticAbility) ability).setEffectIdManually(); } return ability; }