diff --git a/Mage.Sets/src/mage/cards/i/InfectiousCurse.java b/Mage.Sets/src/mage/cards/i/InfectiousCurse.java index ab6ebe36e52..46e353fbd4d 100644 --- a/Mage.Sets/src/mage/cards/i/InfectiousCurse.java +++ b/Mage.Sets/src/mage/cards/i/InfectiousCurse.java @@ -1,9 +1,6 @@ - package mage.cards.i; -import java.util.UUID; import mage.abilities.Ability; -import mage.abilities.Mode; import mage.abilities.SpellAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleStaticAbility; @@ -18,20 +15,23 @@ import mage.constants.*; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; +import mage.game.stack.Spell; import mage.players.Player; -import mage.target.Target; import mage.target.TargetPlayer; import mage.target.targetpointer.FixedTarget; import mage.util.CardUtil; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + /** - * * @author halljared */ public final class InfectiousCurse extends CardImpl { public InfectiousCurse(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},""); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, ""); this.subtype.add(SubType.AURA, SubType.CURSE); this.color.setBlack(true); @@ -46,6 +46,7 @@ public final class InfectiousCurse extends CardImpl { // Spells you cast that target enchanted player cost {1} less to cast. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new InfectiousCurseCostReductionEffect())); + // At the beginning of enchanted player's upkeep, that player loses 1 life and you gain 1 life. InfectiousCurseAbility curseAbility = new InfectiousCurseAbility(); curseAbility.addEffect(new GainLifeEffect(1)); @@ -121,23 +122,31 @@ class InfectiousCurseCostReductionEffect extends CostModificationEffectImpl { @Override public boolean applies(Ability abilityToModify, Ability source, Game game) { - if (abilityToModify instanceof SpellAbility) { - if (source.isControlledBy(abilityToModify.getControllerId())) { - for (UUID modeId : abilityToModify.getModes().getSelectedModes()) { - Mode mode = abilityToModify.getModes().get(modeId); - for (Target target : mode.getTargets()) { - for (UUID targetUUID : target.getTargets()) { - Permanent enchantment = game.getPermanent(source.getSourceId()); - UUID attachedTo = enchantment.getAttachedTo(); - if (targetUUID.equals(attachedTo)) { - return true; - } - } - } - } - } + if (!(abilityToModify instanceof SpellAbility)) { + return false; } - return false; + + if (!source.isControlledBy(abilityToModify.getControllerId())) { + return false; + } + + Permanent enchantment = game.getPermanent(source.getSourceId()); + if (enchantment == null || enchantment.getAttachedTo() == null) { + return false; + } + + Spell spell = (Spell) game.getStack().getStackObject(abilityToModify.getId()); + Set allTargets; + if (spell != null) { + // real cast + allTargets = CardUtil.getAllSelectedTargets(abilityToModify, game); + } else { + // playable + allTargets = CardUtil.getAllPossibleTargets(abilityToModify, game); + } + + // try to reduce all the time (if it possible to target that player) + return allTargets.stream().anyMatch(target -> Objects.equals(target, enchantment.getAttachedTo())); } @Override diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/FeldonOfTheThirdPathTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/FeldonOfTheThirdPathTest.java index 00330d4932e..4e4f13b7502 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/copy/FeldonOfTheThirdPathTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/copy/FeldonOfTheThirdPathTest.java @@ -1,9 +1,8 @@ - - package org.mage.test.cards.copy; import mage.constants.PhaseStep; import mage.constants.Zone; +import org.junit.Assert; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -14,7 +13,6 @@ import org.mage.test.serverside.base.CardTestPlayerBase; * addition to its other types. It gains haste. Sacrifice it at the beginning of * the next end step. * - * * @author LevelX2 */ public class FeldonOfTheThirdPathTest extends CardTestPlayerBase { @@ -22,27 +20,41 @@ public class FeldonOfTheThirdPathTest extends CardTestPlayerBase { /** * Checking that enters the battlefield abilities of the copied creature * card works. - * */ @Test public void testETBEffect() { - // When Highway Robber enters the battlefield, target opponent loses 2 life and you gain 2 life. - addCard(Zone.GRAVEYARD, playerA, "Highway Robber", 1); + // {2}{R}, {T}: Create a token that's a copy of target creature card in your graveyard, except it's an artifact + // in addition to its other types. It gains haste. Sacrifice it at the beginning of the next end step. addCard(Zone.BATTLEFIELD, playerA, "Feldon of the Third Path", 1); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + // + // When Highway Robber enters the battlefield, target opponent loses 2 life and you gain 2 life. + addCard(Zone.GRAVEYARD, playerA, "Highway Robber", 1); + // + addCard(Zone.HAND, playerA, "Lightning Bolt", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); - activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerA, - "{2}{R}, {T}: Create a token that's a copy of target creature card in your graveyard, except it's an artifact in addition to its other types. It gains haste. Sacrifice it at the beginning of the next end step.", - "Highway Robber"); + activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}{R}, {T}: Create a token", "Highway Robber"); + addTarget(playerA, playerB); // opponent to loses 2 life + waitStackResolved(2, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("must have token", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "Highway Robber", 1); + checkPermanentCount("must have card", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "Feldon of the Third Path", 1); + + // destroy token + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Highway Robber"); + waitStackResolved(2, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("must haven't token", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "Highway Robber", 0); + + setStrictChooseMode(true); setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); execute(); - - assertPermanentCount(playerA, "Highway Robber", 1); - assertPermanentCount(playerA, "Feldon of the Third Path", 1); + assertAllCommandsUsed(); assertLife(playerA, 22); // +2 from Robber assertLife(playerB, 18); // -2 from Robber + // possible bug: triggers from destroyed permanents keeps in game state (e.g. 2 triggers in game state) + Assert.assertEquals("game state must have only 1 trigger from original card", 1, currentGame.getState().getTriggers().size()); } @Test @@ -59,8 +71,11 @@ public class FeldonOfTheThirdPathTest extends CardTestPlayerBase { "{2}{R}, {T}: Create a token that's a copy of target creature card in your graveyard, except it's an artifact in addition to its other types. It gains haste. Sacrifice it at the beginning of the next end step.", "Sepulchral Primordial"); addTarget(playerA, "Silvercoat Lion"); // target for ETB Sepulchral Primordial + + setStrictChooseMode(true); setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); execute(); + assertAllCommandsUsed(); assertPermanentCount(playerA, "Feldon of the Third Path", 1); assertPermanentCount(playerA, "Sepulchral Primordial", 1); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/CostModificationTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/CostModificationTest.java index e7a6d7232de..a7b09ac9edd 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/CostModificationTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/CostModificationTest.java @@ -343,4 +343,43 @@ public class CostModificationTest extends CardTestPlayerBase { assertGraveyardCount(playerA, "Engulfing Flames", 1); } + + @Test + public void test_ThatTargetEnchantedPlayer_InfectiousCurse() { + // When Accursed Witch dies, return it to the battlefield transformed under your control attached to target opponent. + // + // transformed: Infectious Curse + // Enchant player + // Spells you cast that target enchanted player cost {1} less to cast. + addCard(Zone.BATTLEFIELD, playerA, "Accursed Witch", 1); // 4/2 + // + addCard(Zone.HAND, playerA, "Lightning Bolt", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + // + // {1}{R} SORCERY + // Grapeshot deals 1 damage to any target. + addCard(Zone.HAND, playerA, "Grapeshot"); + + checkPlayableAbility("no mana", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Grapeshot", false); + + // transform + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Accursed Witch"); + addTarget(playerA, playerB); // attach curse to + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("transformed", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Infectious Curse", 1); + checkPlayableAbility("reduced, but no mana", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Grapeshot", false); + + // cast + // possible bug: transform command can generate duplicated triggers (player get choose trigger dialog but must not) + checkPlayableAbility("reduced, with mana", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Grapeshot", true); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Grapeshot", playerB); + waitStackResolved(3, PhaseStep.PRECOMBAT_MAIN); + checkLife("a", 3, PhaseStep.PRECOMBAT_MAIN, playerA, 20 + 1); // +1 from curse on turn 2 + checkLife("b", 3, PhaseStep.PRECOMBAT_MAIN, playerB, 20 - 1 - 1); // -1 from grapeshot, -1 from curse on turn 2 + + setStrictChooseMode(true); + setStopAt(6, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } } diff --git a/Mage/src/main/java/mage/abilities/effects/ApplyCountersEffect.java b/Mage/src/main/java/mage/abilities/effects/ApplyCountersEffect.java index f13de04f820..3d287de846a 100644 --- a/Mage/src/main/java/mage/abilities/effects/ApplyCountersEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/ApplyCountersEffect.java @@ -30,7 +30,7 @@ public class ApplyCountersEffect extends ContinuousEffectImpl { if (layer == Layer.AbilityAddingRemovingEffects_6) { for (Permanent permanent : game.getBattlefield().getAllActivePermanents()) { for (AbilityCounter counter : permanent.getCounters(game).getAbilityCounters()) { - permanent.addAbility(counter.getAbility(), source == null ? null : source.getSourceId(), game); + permanent.addAbility(counter.getAbility(), source == null ? permanent.getId() : source.getSourceId(), game); } } } diff --git a/Mage/src/main/java/mage/abilities/keyword/TransformAbility.java b/Mage/src/main/java/mage/abilities/keyword/TransformAbility.java index 8c1022e1229..6e45eccd8a5 100644 --- a/Mage/src/main/java/mage/abilities/keyword/TransformAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/TransformAbility.java @@ -63,7 +63,9 @@ public class TransformAbility extends SimpleStaticAbility { permanent.setExpansionSetCode(sourceCard.getExpansionSetCode()); permanent.getAbilities().clear(); for (Ability ability : sourceCard.getAbilities()) { - permanent.addAbility(ability, source == null ? null : source.getSourceId(), game); + // source == null -- call from init card (e.g. own abilities) + // source != null -- from apply effect + permanent.addAbility(ability, source == null ? permanent.getId() : source.getSourceId(), game); } permanent.getPower().modifyBaseValue(sourceCard.getPower().getValue()); permanent.getToughness().modifyBaseValue(sourceCard.getToughness().getValue()); diff --git a/Mage/src/main/java/mage/game/permanent/PermanentToken.java b/Mage/src/main/java/mage/game/permanent/PermanentToken.java index 48c4f4bae66..b97a168e0f4 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentToken.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentToken.java @@ -58,6 +58,7 @@ public class PermanentToken extends PermanentImpl { this.abilities.addAll(token.getAbilities()); } else { // first time -> create ContinuousEffects only once + // so sourceId must be null (keep triggered abilities forever?) for (Ability ability : token.getAbilities()) { this.addAbility(ability, null, game); }